Skip to content

Commit cce5762

Browse files
committed
cc.md: Update error messages and change LazyList to LzyList
1 parent 8be05c4 commit cce5762

File tree

1 file changed

+36
-53
lines changed
  • docs/_docs/reference/experimental

1 file changed

+36
-53
lines changed

docs/_docs/reference/experimental/cc.md

Lines changed: 36 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ followed by `^`. We'll see that this turns the parameter into a _capability_ who
4343

4444
If we now try to define the problematic value `later`, we get a static error:
4545
```
46-
|val later = usingLogFile { f => () => f.write(0) } // error
47-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
48-
|Found: (f: java.io.FileOutputStream^?) ->? () ->{f} Unit
49-
|Required: java.io.FileOutputStream^ => () ->? Unit
46+
| val later = usingLogFile { f => () => f.write(0) }
47+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
48+
| Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit
49+
| Required: java.io.FileOutputStream^ => () ->'s3 Unit
5050
|
51-
|Note that capability f cannot be included in outer capture set ?.
51+
| Note that capability f cannot be included in outer capture set 's3.
5252
```
5353
In this case, it was easy to see that the `logFile` capability escapes in the closure passed to `usingLogFile`. But capture checking also works for more complex cases.
5454
For instance, capture checking is able to distinguish between the following safe code:
@@ -60,11 +60,11 @@ val xs = usingLogFile { f =>
6060
and the following unsafe one:
6161
```scala
6262
val xs = usingLogFile { f =>
63-
LazyList(1, 2, 3).map { x => f.write(x); x * x }
63+
LzyList(1, 2, 3).map { x => f.write(x); x * x }
6464
}
6565
```
6666
An error would be issued in the second case, but not the first one (this assumes a capture-aware
67-
formulation of `LazyList` which we will present later in this page).
67+
formulation `LzyList` of lazily evaluated lists, which we will present later in this page).
6868

6969
It turns out that capture checking has very broad applications. Besides the various
7070
try-with-resources patterns, it can also be a key part to the solutions of many other long standing problems in programming languages. Among them:
@@ -89,12 +89,12 @@ The capture checker extension introduces a new kind of types and it enforces som
8989
Capture checking is done in terms of _capturing types_ of the form
9090
`T^{c₁, ..., cᵢ}`. Here `T` is a type, and `{c₁, ..., cᵢ}` is a _capture set_ consisting of references to capabilities `c₁, ..., cᵢ`.
9191

92-
A _capability_ is syntactically a method- or class-parameter, a local variable, or the `this` of an enclosing class. The type of a capability
92+
An _object capability_ is syntactically a method- or class-parameter, a local variable, or the `this` of an enclosing class. The type of a capability
9393
must be a capturing type with a non-empty capture set. We also say that
9494
variables that are capabilities are _tracked_.
9595

9696
In a sense, every
97-
capability gets its authority from some other, more sweeping capability which it captures. The most sweeping capability, from which ultimately all others are derived is written `cap`. We call it the _universal capability_.
97+
capability gets its authority from some other, more sweeping capability which it captures. The recursion stops with a _universal capability_, written `cap`, from which all other capabilities are ultimately derived.
9898
If `T` is a type, then `T^` is a shorthand for `T^{cap}`, meaning `T` can capture arbitrary capabilities.
9999

100100
Here is an example:
@@ -107,8 +107,8 @@ class Logger(fs: FileSystem^):
107107
def test(fs: FileSystem^) =
108108
val l: Logger^{fs} = Logger(fs)
109109
l.log("hello world!")
110-
val xs: LazyList[Int]^{l} =
111-
LazyList.from(1)
110+
val xs: LzyList[Int]^{l} =
111+
LzyList.from(1)
112112
.map { i =>
113113
l.log(s"computing elem # $i")
114114
i * i
@@ -120,9 +120,9 @@ and retained as a field in class `Logger`. Hence, the local variable `l` has typ
120120
`Logger^{fs}`: it is a `Logger` which retains the `fs` capability.
121121

122122
The second variable defined in `test` is `xs`, a lazy list that is obtained from
123-
`LazyList.from(1)` by logging and mapping consecutive numbers. Since the list is lazy,
123+
`LzyList.from(1)` by logging and mapping consecutive numbers. Since the list is lazy,
124124
it needs to retain the reference to the logger `l` for its computations. Hence, the
125-
type of the list is `LazyList[Int]^{l}`. On the other hand, since `xs` only logs but does
125+
type of the list is `LzyList[Int]^{l}`. On the other hand, since `xs` only logs but does
126126
not do other file operations, it retains the `fs` capability only indirectly. That's why
127127
`fs` does not show up in the capture set of `xs`.
128128

@@ -140,7 +140,7 @@ This type is a shorthand for `(A -> B)^{c, d}`, i.e. the function type `A -> B`
140140
The impure function type `A => B` is treated as an alias for `A ->{cap} B`. That is, impure functions are functions that can capture anything.
141141

142142
A capture annotation `^` binds more strongly than a function arrow. So
143-
`A -> B^{c}` is read as `A -> (B^{c})`.
143+
`A -> B^{c}` is read as `A -> (B^{c})` and `A -> B^` is read as `A -> (B^{cap})`.
144144

145145
Analogous conventions apply to context function types. `A ?=> B` is an impure context function, with `A ?-> B` as its pure complement.
146146

@@ -205,15 +205,15 @@ we have
205205
The set consisting of the root capability `{cap}` covers every other capture set. This is
206206
a consequence of the fact that, ultimately, every capability is created from `cap`.
207207

208-
**Example 2.** Consider again the FileSystem/Logger example from before. `LazyList[Int]` is a proper subtype of `LazyList[Int]^{l}`. So if the `test` method in that example
209-
was declared with a result type `LazyList[Int]`, we'd get a type error. Here is the error message:
208+
**Example 2.** Consider again the FileSystem/Logger example from before. `LzyList[Int]` is a proper subtype of `LzyList[Int]^{l}`. So if the `test` method in that example
209+
was declared with a result type `LzyList[Int]`, we'd get a type error. Here is the error message:
210210
```
211-
11 |def test(using fs: FileSystem^): LazyList[Int] = {
211+
11 |def test(using fs: FileSystem^): LzyList[Int] = {
212212
| ^
213-
| Found: LazyList[Int]^{fs}
214-
| Required: LazyList[Int]
213+
| Found: LzyList[Int]^{fs}
214+
| Required: LzyList[Int]
215215
```
216-
Why does it say `LazyList[Int]^{fs}` and not `LazyList[Int]^{l}`, which is, after all, the type of the returned value `xs`? The reason is that `l` is a local variable in the body of `test`, so it cannot be referred to in a type outside that body. What happens instead is that the type is _widened_ to the smallest supertype that does not mention `l`. Since `l` has capture set `fs`, we have that `{fs}` covers `{l}`, and `{fs}` is acceptable in a result type of `test`, so `{fs}` is the result of that widening.
216+
Why does it say `LzyList[Int]^{fs}` and not `LzyList[Int]^{l}`, which is, after all, the type of the returned value `xs`? The reason is that `l` is a local variable in the body of `test`, so it cannot be referred to in a type outside that body. What happens instead is that the type is _widened_ to the smallest supertype that does not mention `l`. Since `l` has capture set `fs`, we have that `{fs}` covers `{l}`, and `{fs}` is acceptable in a result type of `test`, so `{fs}` is the result of that widening.
217217
This widening is called _avoidance_; it is not specific to capture checking but applies to all variable references in Scala types.
218218

219219
## Capability Classes
@@ -363,39 +363,37 @@ This principle plays an important part in making capture checking concise and pr
363363

364364
## Escape Checking
365365

366-
Some capture sets are restricted so that
367-
they are not allowed to contain the universal capability.
368366

369-
Specifically, if a capturing type is an instance of a type variable, that capturing type
370-
is not allowed to carry the universal capability `cap`. There's a connection to tunnelling here.
371-
The capture set of a type has to be present in the environment when a type is instantiated from
372-
a type variable. But `cap` is not itself available as a global entity in the environment. Hence,
373-
an error should result.
367+
Capabilities follow the usual scoping discipline, which means that capture sets
368+
can contain only capabilities that are visible at the point where the set is defined.
374369

375-
We can now reconstruct how this principle produced the error in the introductory example, where
370+
We now reconstruct how this principle produced the error in the introductory example, where
376371
`usingLogFile` was declared like this:
377372
```scala
378373
def usingLogFile[T](op: FileOutputStream^ => T): T = ...
379374
```
380375
The error message was:
381376
```
382377
| val later = usingLogFile { f => () => f.write(0) }
383-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
384-
|The expression's type () => Unit is not allowed to capture the root capability `cap`.
385-
|This usually means that a capability persists longer than its allowed lifetime.
378+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
379+
| Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit
380+
| Required: java.io.FileOutputStream^ => () ->'s3 Unit
381+
|
382+
| Note that capability f cannot be included in outer capture set 's3.
386383
```
387384
This error message was produced by the following logic:
388385

389386
- The `f` parameter has type `FileOutputStream^`, which makes it a capability.
390387
- Therefore, the type of the expression `() => f.write(0)` is `() ->{f} Unit`.
391388
- This makes the type of the whole closure passed to `usingLogFile` the dependent function type
392-
`(f: FileOutputStream^) -> () ->{f} Unit`.
389+
`(f: FileOutputStream^'s1) ->'s2 () ->{f} Unit`,
390+
for some as yet uncomputed capture sets `'s1` and `'s2`.
393391
- The expected type of the closure is a simple, parametric, impure function type `FileOutputStream^ => T`,
394392
for some instantiation of the type variable `T`.
395-
- The smallest supertype of the closure's dependent function type that is a parametric function type is
396-
`FileOutputStream^ => () ->{cap} Unit`
397-
- Hence, the type variable `T` is instantiated to `() ->{cap} Unit`, or abbreviated `() => Unit`,
398-
which causes the error.
393+
- Matching with the found type, `T` must have the shape `() ->'s3 Unit`, for
394+
some capture set `'s3` defined at the level of value `later`.
395+
- That capture set cannot include the capability `f` since `f` is locally bound.
396+
This causes the error.
399397

400398
An analogous restriction applies to the type of a mutable variable.
401399
Another way one could try to undermine capture checking would be to
@@ -408,23 +406,8 @@ usingLogFile { f =>
408406
}
409407
loophole()
410408
```
411-
But this will not compile either, since mutable variables cannot have universal capture sets.
412-
413-
One also needs to prevent returning or assigning a closure with a local capability in an argument of a parametric type. For instance, here is a
414-
slightly more refined attack:
415-
```scala
416-
class Cell[+A](x: A)
417-
val sneaky = usingLogFile { f => Cell(() => f.write(0)) }
418-
sneaky.x()
419-
```
420-
At the point where the `Cell` is created, the capture set of the argument is `f`, which
421-
is OK. But at the point of use, it is `cap` (because `f` is no longer in scope), which causes again an error:
422-
```
423-
| sneaky.x()
424-
| ^^^^^^^^
425-
|The expression's type () => Unit is not allowed to capture the root capability `cap`.
426-
|This usually means that a capability persists longer than its allowed lifetime.
427-
```
409+
But this will not compile either, since the capture set of the mutable variable `loophole` cannot refer to variable `f`, which is not visible
410+
where `loophole` is defined.
428411

429412
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_:
430413

0 commit comments

Comments
 (0)