You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/_docs/reference/experimental/capture-checking/basics.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -347,6 +347,8 @@ loophole()
347
347
But this will not compile either, since the capture set of the mutable variable `loophole` cannot refer to variable `f`, which is not visible
348
348
where `loophole` is defined.
349
349
350
+
### Monotonicity Rule
351
+
350
352
Looking at object graphs, we observe a monotonicity property: The capture set of an object `x` covers the capture sets of all objects reachable through `x`. This property is reflected in the type system by the following _monotonicity rule_:
351
353
352
354
- In a class `C` with a field `f`, the capture set `{this}` covers the capture set `{this.f}` as well as the capture set of any application of `this.f` to pure arguments.
Copy file name to clipboardExpand all lines: docs/_docs/reference/experimental/capture-checking/classes.md
+35-6Lines changed: 35 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -45,7 +45,9 @@ Here class `Super` has local capability `a`, which gets inherited by class
45
45
`Sub` and is combined with `Sub`'s own local capability `b`. Class `Sub` also has an argument capability corresponding to its parameter `x`. This capability gets instantiated to `c` in the final constructor call `Sub(c)`. Hence,
46
46
the capture set of that call is `{a, b, c}`.
47
47
48
-
The capture set of the type of `this` of a class is inferred by the capture checker, unless the type is explicitly declared with a self type annotation like this one:
48
+
## Capture Set of This
49
+
50
+
The capture set of the type of `this` of a class is inferred by the capture checker, unless the type is explicitly declared with a self-type annotation like this one:
49
51
```scala
50
52
classC:
51
53
self: D^{a, b} => ...
@@ -73,7 +75,34 @@ we know that the type of `this` must be pure, since `this` is the right hand sid
73
75
| of the enclosing class A
74
76
```
75
77
76
-
## Capture Tunnelling
78
+
### Traits and Open Classes
79
+
80
+
The self-type inference behaves differently depending on whether all subclasses of a class are known. For a regular (non-open, non-abstract) class, all subclasses are known at compile time, so the capture checker can precisely infer the self-type. However, for traits, abstract classes, and [`open`](../other-new-features/open-classes.md) classes, arbitrary subclasses may exist, so the capture checker conservatively assumes that `this` may capture capabilities.
81
+
82
+
For example:
83
+
```scala
84
+
classA:
85
+
deffn:A=this// ok
86
+
87
+
traitB:
88
+
deffn:B=this// error
89
+
deffn2:B^=this// ok
90
+
91
+
abstractclassC:
92
+
deffn:C=this// error
93
+
deffn2:C^=this// ok
94
+
95
+
sealedabstractclassD:
96
+
deffn:D=this// ok
97
+
98
+
objectD0extendsD
99
+
100
+
openclassE:
101
+
deffn:E=this// error
102
+
deffn2:E^=this// ok
103
+
```
104
+
105
+
## Capture Tunneling
77
106
78
107
Consider the following simple definition of a `Pair` class:
This might seem surprising. The `Pair(x, y)` value does capture capabilities `ct` and `fs`. Why don't they show up in its type at the outside?
95
124
96
-
The answer is capture tunnelling. Once a type variable is instantiated to a capturing type, the
125
+
The answer is capture tunneling. Once a type variable is instantiated to a capturing type, the
97
126
capture is not propagated beyond this point. On the other hand, if the type variable is instantiated
98
127
again on access, the capture information "pops out" again. For instance, even though `p` is technically pure because its capture set is empty, writing `p.fst` would record a reference to the captured capability `ct`. So if this access was put in a closure, the capability would again form part of the outer capture set. E.g.
99
128
```scala
@@ -144,7 +173,7 @@ end LzyCons
144
173
The `LzyCons` class takes two parameters: A head `hd` and a tail `tl`, which is a function
145
174
returning a `LzyList`. Both the function and its result can capture arbitrary capabilities.
146
175
The result of applying the function is memoized after the first dereference of `tail` in
147
-
the private mutable field `cache`. Note that the typing of the assignment `cache = tl()` relies on the monotonicity rule for `{this}` capture sets.
176
+
the private mutable field `cache`. Note that the typing of the assignment `cache = tl()` relies on the [monotonicity rule](basics.md#monotonicity-rule) for `{this}` capture sets.
148
177
149
178
Here is an extension method to define an infix cons operator `#:` for lazy lists. It is analogous
150
179
to `::` but instead of a strict list it produces a lazy list without evaluating its right operand.
@@ -204,7 +233,7 @@ Their capture annotations are all as one would expect:
204
233
- Filtering a lazy list produces a lazy list that captures the original list as well
205
234
as the (possibly impure) filtering predicate.
206
235
- Concatenating two lazy lists produces a lazy list that captures both arguments.
207
-
- Dropping elements from a lazy list gives a safe approximation where the original list is captured in the result. In fact, it's only some suffix of the list that is retained at run time, but our modelling identifies lazy lists and their suffixes, so this additional knowledge would not be useful.
236
+
- Dropping elements from a lazy list gives a safe approximation where the original list is captured in the result. In fact, it's only some suffix of the list that is retained at run time, but our modeling identifies lazy lists and their suffixes, so this additional knowledge would not be useful.
208
237
209
238
Of course the function passed to `map` or `filter` could also be pure. After all, `A -> B` is a subtype of `(A -> B)^{cap}` which is the same as `A => B`. In that case, the pure function
210
239
argument will _not_ show up in the result type of `map` or `filter`. For instance:
@@ -218,7 +247,7 @@ argument does not show up since it is pure. Likewise, if the lazy list
218
247
This demonstrates that capability-based
219
248
effect systems with capture checking are naturally _effect polymorphic_.
220
249
221
-
This concludes our example. It's worth mentioning that an equivalent program defining and using standard, strict lists would require no capture annotations whatsoever. It would compile exactly as written now in standard Scala 3, yet one gets the capture checking for free. Essentially, `=>` already means "can capture anything" and since in a strict list side effecting operations are not retained in the result, there are no additional captures to record. A strict list could of course capture side-effecting closures in its elements but then tunnelling applies, since
250
+
This concludes our example. It's worth mentioning that an equivalent program defining and using standard, strict lists would require no capture annotations whatsoever. It would compile exactly as written now in standard Scala 3, yet one gets the capture checking for free. Essentially, `=>` already means "can capture anything" and since in a strict list side effecting operations are not retained in the result, there are no additional captures to record. A strict list could of course capture side-effecting closures in its elements but then tunneling applies, since
222
251
these elements are represented by a type variable. This means we don't need to annotate anything there either.
223
252
224
253
Another possibility would be a variant of lazy lists that requires all functions passed to `map`, `filter` and other operations like it to be pure. E.g. `map` on such a list would be defined like this:
Copy file name to clipboardExpand all lines: docs/_docs/reference/experimental/capture-checking/internals.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,7 +34,7 @@ that gets propagated and resolved further out.
34
34
35
35
When a mapping `m` is performed on a capture set variable `C`, a new variable `Cm` is created that contains the mapped elements and that is linked with `C`. If `C` subsequently acquires further elements through propagation, these are also propagated to `Cm` after being transformed by the `m` mapping. `Cm` also gets the same supersets as `C`, mapped again using `m`.
36
36
37
-
One interesting aspect of the capture checker concerns the implementation of capture tunnelling. The [foundational theory](https://infoscience.epfl.ch/record/290885) on which capture checking is based makes tunnelling explicit through so-called _box_ and
37
+
One interesting aspect of the capture checker concerns the implementation of capture tunneling. The [foundational theory](https://infoscience.epfl.ch/record/290885) on which capture checking is based makes tunneling explicit through so-called _box_ and
38
38
_unbox_ operations. Boxing hides a capture set and unboxing recovers it. The capture checker inserts virtual box and unbox operations based on actual and expected types similar to the way the type checker inserts implicit conversions. When capture set variables are first introduced, any capture set in a capturing type that is an instance of a type parameter instance is marked as "boxed". A boxing operation is
39
39
inserted if the expected type of an expression is a capturing type with
40
40
a boxed capture set variable. The effect of the insertion is that any references
0 commit comments