Skip to content

Commit adff7dc

Browse files
committed
Some tweaks to modularity.md
1 parent 0ff4f41 commit adff7dc

File tree

1 file changed

+10
-12
lines changed

1 file changed

+10
-12
lines changed

docs/_docs/reference/experimental/modularity.md

+10-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/modularity.
99
Martin Odersky, 7.1.2024
1010

1111
Scala is a language in the SML tradition, in the sense that it has
12-
abstract and alias types as members of classes. This leads to a simple dependently
12+
abstract and alias types as members of modules (which in Scala take the form of objects and classes). This leads to a simple dependently
1313
typed system, where dependencies in types are on paths instead of full terms.
1414

1515
So far, some key ingredients were lacking which meant that module composition with functors is harder in Scala than in SML. In particular, one often needs to resort the infamous `Aux` pattern that lifts type members into type parameters so that they can be tracked across class instantiations. This makes modular, dependently typed programs
@@ -19,10 +19,10 @@ In this note I propose some small changes to Scala's dependent typing that makes
1919
modular programming much more straightforward.
2020

2121
The suggested improvements have been implemented and are available
22-
in source version `future` if the additional experimental language import `modularity` is present. For instance, using
22+
in source version `future` if the additional experimental language import `modularity` is present. For instance, using the following command:
2323

2424
```
25-
scala compile -source future -language experimental.modularity
25+
scala compile -source:future -language:experimental.modularity
2626
```
2727

2828
## Tracked Parameters
@@ -41,7 +41,7 @@ For instance, consider the following definitions:
4141
```
4242
Then `f(y)` would have type `Int`, since the compiler will substitute the
4343
concrete parameter reference `y` for the formal parameter `x` in the result
44-
type of `f`.
44+
type of `f`, and `y.T = Int`
4545

4646
However, if we use a class `F` instead of a method `f`, things go wrong.
4747

@@ -66,7 +66,7 @@ Then the constructor `F` would get roughly the following type:
6666
```scala
6767
F(x1: C): F { val x: x1.type }
6868
```
69-
_Aside:_ More precisely, both parameter and refinement would apply to the same name `x` but the refinement still refers to the parameter. We unfortunately can't express that in source, however, so we chose the new name `x1` for the parameter.
69+
_Aside:_ More precisely, both parameter and refinement would apply to the same name `x` but the refinement still refers to the parameter. We unfortunately can't express that in source, however, so we chose the new name `x1` for the parameter in the explanation.
7070

7171
With the new constructor type, the expression `F(y).result` would now have the type `Int`, as hoped for. The reasoning to get there is as follows:
7272

@@ -118,23 +118,21 @@ The (soft) `tracked` modifier is only allowed for `val` parameters of classes.
118118

119119
**Discussion**
120120

121-
Since `tracked` is so useful, why not assume it by default? First, `tracked` makes sense only for `val` parameters. If a class parameter is not also a field declared using `val` then there's nothing to refine in the constructor.
122-
But making all `val` parameters tracked by default would also be a backwards
123-
incompatible change. For instance, the following code would break:
121+
Since `tracked` is so useful, why not assume it by default? First, `tracked` makes sense only for `val` parameters. If a class parameter is not also a field declared using `val` then there's nothing to refine in the constructor result type. One could think of at least making all `val` parameters tracked by default, but that would be a backwards incompatible change. For instance, the following code would break:
124122

125123
```scala
126124
case class Foo(x: Int)
127125
var foo = Foo(1)
128126
if someCondition then foo = Foo(2)
129127
```
130-
if we assume `tracked` for parameter `x` (which is implicitly a `val`),
128+
If we assume `tracked` for parameter `x` (which is implicitly a `val`),
131129
then `foo` would get inferred type `Foo { val x: 1 }`, so it could not
132-
be reassigned to a value of type `Foo { val x: 2 }`.
130+
be reassigned to a value of type `Foo { val x: 2 }` on the next line.
133131

134132
Another approach might be to assume `tracked` for a `val` parameter `x`
135133
only if the class refers to a type member of `x`. But it turns out that this
136134
scheme is unimplementable since it would quickly lead to cyclic references
137-
in recursive class graphs. So an explicit `tracked` looks like the best feasible option.
135+
when typechecking recursive class graphs. So an explicit `tracked` looks like the best available option.
138136

139137
## Allow Class Parents to be Refined Types
140138

@@ -186,6 +184,6 @@ The rules for export forwarders are changed as follows.
186184

187185
Previously, all export forwarders were declared `final`. Now, only term members are declared `final`. Type aliases are left aside.
188186

189-
This makes it possible to export the same type member into several traits and then mix these traits in the same class. `typeclass-aggregates.scala` shows why this is essential to be able to combine multiple givens with type members.
187+
This makes it possible to export the same type member into several traits and then mix these traits in the same class. The test file `tests/pos/typeclass-aggregates.scala` shows why this is essential if we want to combine multiple givens with type members in a new given that aggregates all these givens in an intersection type.
190188

191189
The change does not lose safety since different type aliases would in any case lead to uninstantiatable classes.

0 commit comments

Comments
 (0)