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/cc.md
+132-9
Original file line number
Diff line number
Diff line change
@@ -216,13 +216,13 @@ This widening is called _avoidance_; it is not specific to capture checking but
216
216
217
217
## Capability Classes
218
218
219
-
Classes like `CanThrow` or `FileSystem` have the property that their values are always intended to be capabilities. We can make this intention explicit and save boilerplate by declaring these classes with a `@capability` annotation.
219
+
Classes like `CanThrow` or `FileSystem` have the property that their values are always intended to be capabilities. We can make this intention explicit and save boilerplate by letting these classes extend the `Capability` class defined in object `cap`.
220
220
221
-
The capture set of a capability class type is always `{cap}`. This means we could equivalently express the `FileSystem` and `Logger` classes as follows:
221
+
The capture set of a `Capability` subclass type is always `{cap}`. This means we could equivalently express the `FileSystem` and `Logger` classes as follows:
222
222
```scala
223
-
importannotation.capability
223
+
importcaps.Capability
224
224
225
-
@capability classFileSystem
225
+
classFileSystemextendsCapability
226
226
227
227
classLogger(usingFileSystem):
228
228
deflog(s: String):Unit=???
@@ -290,7 +290,7 @@ The captured references of a class include _local capabilities_ and _argument ca
290
290
the local capabilities of a superclass are also local capabilities of its subclasses. Example:
291
291
292
292
```scala
293
-
@capability classCap
293
+
classCapextends caps.Capability
294
294
295
295
deftest(a: Cap, b: Cap, c: Cap) =
296
296
classSuper(y: Cap):
@@ -317,7 +317,7 @@ The inference observes the following constraints:
317
317
318
318
For instance, in
319
319
```scala
320
-
@capability classCap
320
+
classCapextends caps.Capability
321
321
deftest(c: Cap) =
322
322
classA:
323
323
valx:A=this
@@ -502,7 +502,7 @@ Under the language import `language.experimental.captureChecking`, the code is i
502
502
```
503
503
To integrate exception and capture checking, only two changes are needed:
504
504
505
-
-`CanThrow` is declared as a `@capability` class, so all references to `CanThrow` instances are tracked.
505
+
-`CanThrow` is declared as a class extending `Capability`, so all references to `CanThrow` instances are tracked.
506
506
- Escape checking is extended to `try` expressions. The result type of a `try` is not allowed to
507
507
capture the universal capability.
508
508
@@ -635,9 +635,132 @@ To summarize, there are two "sweet spots" of data structure design: strict lists
635
635
side-effecting or resource-aware code and lazy lists in purely functional code.
636
636
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.
637
637
638
-
## Function Type Shorthands
638
+
## Existential Capabilities
639
639
640
-
TBD
640
+
In fact, what is written as the top type `cap` can mean different capabilities, depending on scope. For instance, consider the function type
641
+
`() -> Iterator[T]^`. This is taken to mean
642
+
```scala
643
+
() ->Exists x. Iterator[T]^x
644
+
```
645
+
In other words, it means an unknown type bound `x` by an "existential" in the scope of the function result. A `cap` in a function result is therefore different from a `cap` at the top-level or in a function parameter.
646
+
647
+
Internally, an existential type is represented as a kind of dependent function type. The type above would be modelled as
648
+
```scala
649
+
() -> (x: Exists) ->Iterator[T]^x
650
+
```
651
+
Here, `Exists` is a sealed trait in the `caps` object that serves to mark
652
+
dependent functions as representations of existentials. It should be noted
653
+
that this is strictly an internal representation. It is explained here because it can show up in error messages. It is generally not recommended to use this syntax in source code. Instead one should rely on the automatic expansion of `^` and `cap` to existentials, which can be
654
+
influenced by introducing the right alias types. The rules for this expansion are as follows:
655
+
656
+
- If a function result type contains covariant occurrences of `cap`,
657
+
we replace these occurrences with a fresh existential variable which
658
+
is bound by a quantifier scoping over the result type.
659
+
- We might want to do the same expansion in function arguments, but right now this is not done.
660
+
- Occurrences of `cap` elsewhere are not translated. They can be seen as representing an existential at the top-level scope.
661
+
662
+
**Examples:**
663
+
664
+
-`A => B` is an alias type that expands to `(A -> B)^`, therefore
665
+
`() -> A => B` expands to `() -> Exists c. A ->{c} B`.
666
+
667
+
-`() -> Iterator[A => B]` expands to `() -> Exists c. Iterator[A ->{c} B]`
668
+
669
+
-`A -> B^` expands to `A -> Exists c.B^{c}`.
670
+
671
+
- If we define `type Fun[T] = A -> T`, then `() -> Fun[B^]` expands to `() -> Exists c.Fun[B^{c}]`, which dealiases to `() -> Exists c.A -> B^{c}`. This demonstrates how aliases can be used to force existential binders to be in some specific outer scope.
672
+
673
+
- If we define
674
+
```scala
675
+
typeF=A->Fun[B^]
676
+
```
677
+
then the type alias expands to
678
+
```scala
679
+
typeF=A->Exists c.A->B^{c}
680
+
```
681
+
682
+
**Typing Rules:**
683
+
684
+
- When we typecheck the body of a function or method, any covariant occurrences of `cap` in the result type are bound with a fresh existential.
685
+
- Conversely, when we typecheck the application of a function or method,
686
+
with an existential result type `Exists ex.T`, the result of the application is `T` where every occurrence of the existentially bound
687
+
variable `ex` is replaced by `cap`.
688
+
689
+
## Reach Capabilities
690
+
691
+
Say you have a method `f` that takes an impure function argument which gets stored in a `var`:
692
+
```scala
693
+
deff(op: A=>B)
694
+
varx:A->{op} B= op
695
+
...
696
+
```
697
+
This is legal even though `var`s cannot have types with `cap` or existential capabilities. The trick is that the type of the variable `x`
698
+
is not `A => B` (this would be rejected), but is the "narrowed" type
699
+
`A ->{op} B`. In other words, all capabilities retained by values of `x`
700
+
are all also referred to by `op`, which justifies the replacement of `cap` by `op`.
701
+
702
+
A more complicated situation is if we want to store successive values
703
+
held in a list. Example:
704
+
```scala
705
+
deff(ops: List[A=>B])
706
+
varxs= ops
707
+
varx:???= xs.head
708
+
while xs.nonEmpty do
709
+
xs = xs.tail
710
+
x = xs.head
711
+
...
712
+
```
713
+
Here, `x` cannot be given a type with an `ops` capability. In fact, `ops` is pure, i.e. it's capture set is empty, so it cannot be used as the name of a capability. What we would like to express is that `x` refers to
714
+
any operation "reachable" through `ops`. This can be expressed using a
715
+
_reach capability_`ops*`.
716
+
```scala
717
+
deff(ops: List[A=>B])
718
+
varxs= ops
719
+
varx:A->{ops*} B= xs.head
720
+
...
721
+
```
722
+
Reach capabilities take the form `x*` where `x` is syntactically a regular capability. If `x: T` then `x*` stands for any capability that appears covariantly in `T` and that is accessed through `x`. The least supertype of this capability is the set of all capabilities appearing covariantly in `T`.
723
+
724
+
## Capability Polymorphism
725
+
726
+
It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources
727
+
`Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`:
728
+
```scala
729
+
classSource[X^]:
730
+
privatevarlisteners:Set[Listener^{X^}] =Set.empty
731
+
defregister(x: Listener^{X^}):Unit=
732
+
listeners += x
733
+
734
+
defallListeners:Set[Listener^{X^}] = listeners
735
+
```
736
+
The type variable `X^` can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above
737
+
we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X^`. The `register` method takes a listener of this type
738
+
and assigns it to the variable.
739
+
740
+
Capture set variables `X^` are represented as regular type variables with a
741
+
special upper bound `CapSet`. For instance, `Source` could be equivalently
742
+
defined as follows:
743
+
```scala
744
+
classSource[X<:CapSet^]:
745
+
...
746
+
```
747
+
`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only purpose is to identify capture set type variables and types. Capture set variables can be inferred like regular type variables. When they should be instantiated explicitly one uses a capturing
748
+
type `CapSet`. For instance:
749
+
```scala
750
+
classAsyncextends caps.Capability
751
+
752
+
deflistener(async: Async):Listener^{async} =???
753
+
754
+
deftest1(async1: Async, others: List[Async]) =
755
+
valsrc=Source[CapSet^{async1, others*}]
756
+
...
757
+
```
758
+
Here, `src` is created as a `Source` on which listeners can be registered that refer to the `async` capability or to any of the capabilities in list `others`. So we can continue the example code above as follows:
0 commit comments