Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Exploit capture monotonicity in the apply rule? #14387

odersky started this conversation in CC experiment
Discussion options

Captures have a monotonicity property. The references captured by the result of an evaluation are a subset of the references that captured by the evaluation itself. Specifically in a function call f x, the captures of the result must be subsumed by the union of the captures of f and the captures of x. This motivates a new, more permissive rule for apply:

 E |- f: Cf (Ca Ta -> Cr Tr)
 E |- a: Ca Ta
 ------------------------
 E |- f a: Cr /\ {f, a} Tr

An example where the new rule is helpful is typing lazy lists. Here we have in slightly simplified form:

final class LazyCons[+T](val x: T, val xs: () => {*} LazyList[T]) extends LazyList[T]:
 this: {*} LazyList[T] =>
 def isEmpty = false
 def head = x
 def tail: {this} LazyList[T] = xs()

With the old rule, xs() has capture set {*} so the typing of tail fails. But with the new rule, it would have
capture set {this.xs} /\ {*} = {this.xs}, and this.xs <: this by the path subsumption rule. So it checks out.

The idea is to replace the previous rule #13657 with the new rule, since it has a much clearer semantic motivation.
As a first step, the current implementation specializes the rule for the case where f is a tracked function and the capture set of the argument a is empty. That covers the LazyList example. We should consider generalizing to the case where the function arguments have non-empty capture sets, but that would make capture set computations more complex, so we should also evaluate the performance impact.

You must be logged in to vote

Replies: 3 comments

Comment options

This new rule for application seems to make a lot of sense. It mirrors (app) from the reachability system, so it should be appropriate for us as well.

(削除) There's something slightly strange with the LazyCons example. Shouldn't the type of xs be {*} () => {*} LazyList[T]? Or do we have syntax sugar implemented and () => {*} LazyList[T] is the same as that type? (削除ここまで)
EDIT: OK, I just remembered that in fact we have syntax sugar like that, and we even discussed it before.

Other than that, the example makes sense to me. It's also further evidence that we want paths in capture sets.

You must be logged in to vote
0 replies
Comment options

odersky
Jan 31, 2022
Maintainer Author

One problem with the new rule is that the capture prediction lemma breaks down. In fact xs() will not contain a reference xs. We'd have to somehow interpret the xs in the capture set as "we got there through an application of xs", but it seems that would make reasoning more awkward. That might be a good reason to limit it to simple cases only, i.e require that all arguments in the application are pure.

One way to salvage the capture prediction lemma would be to treat every reference as a proxy for the set of primitive capabilities it could be instantiated with. Under that viewpoint it would be true that xs() and xs represent the same set, and that {f, a} is an upper bound of the set of capabilities captured by f a.

You must be logged in to vote
0 replies
Comment options

odersky
Jan 31, 2022
Maintainer Author

Yet another way to treat the example would be to extend paths to applications. i.e. this.xs() would be a path, and
this.xs() <: this.xs <: this. That would push it more towards full dependent typing.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /