Hands on Capture Checking in Scala
6 points by xvw
6 points by xvw
Can I not use the def
syntax for tracking captures? I tried it on this part of the full code linked in the post:
// `f` captures `a1`, a tracked, free variable it explicitly refers to in its body.
val f: Int ->{a1} Int =
x =>
println(a1)
x + 1
If I change this to
def f2(x: Int): Int^{a1} =
println(a1)
x + 1
I get
[error] ./test.scala:275:19
[error] Int is a pure type, it makes no sense to add a capture set to it
[error] def f2(x: Int): Int^{a1} =
[error] ^^^^^^^^
Having to use the val
syntax is a bit annoying. Is this going to be fixed?
I’m also confused about how to capture other functions. I tried this:
val f1: () -> Unit =
() => ()
val f2: () -> Unit =
() => ()
val f3: () ->{f1, f2} Unit =
() =>
f1()
f2()
Which fails with
[error] ./test.scala:117:15
[error] (f1 : () -> Unit) cannot be tracked since it is not a parameter or local value
[error] val f3: () ->{f1, f2} Unit =
[error] ^^^^^^
I tried annotating the functions with cap
using () ->{cap} Unit
and () -> Unit^{cap}
but no luck.
Is it possible to track captured top-level functions?
You’re running into 3 different gotchas :)
Only values can capture other values. Functions are values, but methods (defs) are not. So what you wrote isn’t the method that captures a1, but the method that returns an Int which captures a1. First gotcha.
Second gotcha: for the same reason that i can’t have a tracking String, you can’t have a tracking Int. Primitive types cannot capture anything, and therefore cannot be tracked (i’m not a fan, they should be able to track cap).
Third gotcha: you cannot track global values, because they have by definition already escaped: they can be accessed from anywhere, by anyone, and do not have a scope to escape from.
These things weren’t in the doc as i learned, and mostly aren’t yet. Do you understand my pain? :)
Thanks, that’s very helpful.
Functions are values, but methods (defs) are not
Wait, so the def
in my code is a method? What’s the receiver type if it’s a method? How do I call it as a method?
The reason why I wanted to capture other functions in a function is that, as far as I understand, one use case for tracking and capabilities is that it can also be used for checked exceptions and effects. You mark your exception or effect type as a “capability” and then track it via capture sets.
But that doesn’t let me track the uses of the type when I use it directly or transitively via a top-level function? I have an example here: https://users.scala-lang.org/t/scala-3-experimental-checked-exceptions-question/10856
The idea with f6
in that code is that it doesn’t list all of the thrown exceptions, but rather lists functions that throw the exceptions. But I just couldn’t make it work but it looks like not a lot of people know about this stuff.
Do you think my code in the link can be fixed? Or am I fundamentally misunderstanding what this is about?
All defs are methods of whatever encloses them, either a class or a potentially synthetic object. In a repl for example, there’s always a hidden wrapping object.
I think you’re doing what just about everyone else is doing because of the confusing names and communication: mixing capture checking and capabilities, which are distinct, unrelated features. Capture checking is strictly about preventing undesired escapes. Capabilities are implicit handlers, which you use to encode effects: all effectful values are functions taking one or more implicit handlers.
Most of the time, you want to track capabilities because they’re stateful (a random number generator, say) and capturing them is going to end in tears. But you can’t replace listing the handlers you take by the handlers you capture. Those are two different informations you give the compiler, used for very different purposes. It’d be like saying that capturing Monad is the same as expecting an implicit Monad instance.
May i just say, i’m enjoying this interaction. Questions here are a lot more insightful than I got just about anywhere else, including at important Scala conferences.