Skip to content

Commit af832ba

Browse files
authored
Merge pull request #14443 from dotty-staging/cc-doc-page-2
Capture checking doc page changes
2 parents d940d8f + 335e0e7 commit af832ba

File tree

1 file changed

+35
-11
lines changed
  • docs/docs/reference/experimental

1 file changed

+35
-11
lines changed

docs/docs/reference/experimental/cc.md

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
layout: doc-page
33
title: "Capture Checking"
4-
movedTo: https://docs.scala-lang.org/scala3/reference/experimental/cc.html
54
---
65

76
Capture checking is a research project that modifies the Scala type system to track references to capabilities in values. It is currently
@@ -145,7 +144,7 @@ is the same as
145144
```
146145
Contrast with
147146
```scala
148-
({c} A) -> ({d} B)) -> C
147+
({c} A) -> ({d} B) -> C
149148
```
150149
which is a curried pure function over argument types that can capture `c` and `d`, respectively.
151150

@@ -437,6 +436,10 @@ is OK. But at the point of use, it is `*` (because `f` is no longer in scope), w
437436
|This usually means that a capability persists longer than its allowed lifetime.
438437
```
439438

439+
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_:
440+
441+
- In a class `C` with a field `f`, the capture set `{this}` covers the capture set `{this.f}` as well as any application of the latter set to pure arguments.
442+
440443
## Checked Exceptions
441444

442445
Scala enables checked exceptions through a language import. Here is an example,
@@ -515,8 +518,6 @@ To integrate exception and capture checking, only two changes are needed:
515518
- Escape checking is extended to `try` expressions. The result type of a `try` is not allowed to
516519
capture the universal capability.
517520

518-
519-
520521
## A Larger Example
521522

522523
As a larger example, we present an implementation of lazy lists and some use cases. For simplicity,
@@ -558,10 +559,10 @@ end LzyCons
558559
The `LzyCons` class takes two parameters: A head `hd` and a tail `tl`, which is a function
559560
returning a `LzyList`. Both the function and its result can capture arbitrary capabilities.
560561
The result of applying the function is memoized after the first dereference of `tail` in
561-
the private mutable field `cache`.
562+
the private mutable field `cache`. Note that the typing of the assignment `cache = tl()` relies on the monotonicity rule for `{this}` capture sets.
562563

563564
Here is an extension method to define an infix cons operator `#:` for lazy lists. It is analogous
564-
to `::` but it produces a lazy list (without evaluating its right operand) instead of a strict list.
565+
to `::` but instead of a strict list it produces a lazy list without evaluating its right operand.
565566
```scala
566567
extension [A](x: A)
567568
def #:(xs1: => {*} LzyList[A]): {xs1} LzyList[A] =
@@ -585,7 +586,7 @@ Here is a use of `tabulate`:
585586
class LimitExceeded extends Exception
586587
def squares(n: Int)(using ct: CanThrow[LimitExceeded]) =
587588
tabulate(10) { i =>
588-
if i > 9 then throw Ex1()
589+
if i > 9 then throw LimitExceeded()
589590
i * i
590591
}
591592
```
@@ -615,8 +616,7 @@ extension [A](xs: {*} LzyList[A])
615616
Their capture annotations are all as one would expect:
616617

617618
- Mapping a lazy list produces a lazy list that captures the original list as well
618-
as the (possibly impure) mapping function. Of course, it would also be possible to
619-
pass a concrete function argument that is pure.
619+
as the (possibly impure) mapping function.
620620
- Filtering a lazy list produces a lazy list that captures the original list as well
621621
as the (possibly impure) filtering predicate.
622622
- Concatenating two lazy lists produces a lazy list that captures both arguments.
@@ -634,7 +634,19 @@ argument does not show up since it is pure. Likewise, if the lazy list
634634
This demonstrates that capability-based
635635
effect systems with capture checking are naturally _effect polymorphic_.
636636

637-
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. This fact is probably one of the greatest plus points of our approach to capture checking.
637+
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
638+
these elements are represented by a type variable. This means we don't need to annotate anything there either.
639+
640+
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:
641+
```scala
642+
extension [A](xs: LzyList[A])
643+
def map[B](f: A -> B): LzyList[B] = ...
644+
```
645+
That variant would not require any capture annotations either.
646+
647+
To summarize, there are two "sweet spots" of data structure design: strict lists in
648+
side-effecting or resource-aware code and lazy lists in purely functional code.
649+
Both are already correctly capture-typed without requiring any explicit annotations. Capture annotations only come into play where the semantics gets more complicated because we deal with delayed effects such as in impure lazy lists or side-effecting iterators over strict lists. This property is probably one of the greatest plus points of our approach to capture checking compared to previous techniques which tend to be more noisy.
638650

639651
## Function Type Shorthands
640652

@@ -682,8 +694,20 @@ that gets propagated and resolved further out.
682694

683695
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`.
684696

697+
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
698+
_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
699+
inserted if the expected type of an expression is a capturing type with
700+
a boxed capture set variable. The effect of the insertion is that any references
701+
to capabilities in the boxed expression are forgotten, which means that capture
702+
propagation is stopped. Dually, if the actual type of an expression has
703+
a boxed variable as capture set, an unbox operation is inserted, which adds all
704+
elements of the capture set to the environment.
705+
706+
Boxing and unboxing has no runtime effect, so the insertion of these operations is only simulated; the only visible effect is the retraction and insertion
707+
of variables in the capture sets representing the environment of the currently checked expression.
708+
685709
The `-Ycc-debug` option provides some insight into the workings of the capture checker.
686-
When it is turned on, capture set variables are printed with an ID and some information about their provenance. For instance, the string `{f, xs}33M5V` indicates a capture set
710+
When it is turned on, boxed sets are marked explicitly and capture set variables are printed with an ID and some information about their provenance. For instance, the string `{f, xs}33M5V` indicates a capture set
687711
variable that is known to hold elements `f` and `xs`. The variable's ID is `33`. The `M`
688712
indicates that the variable was created through a mapping from a variable with ID `5`. The latter is a regular variable, as indicated
689713
by `V`.

0 commit comments

Comments
 (0)