Although I still don't quite understand continuations myself, as best as I can tell, the key issue in your example is that your code does not always supply a shift
to the reset
.
The compiler expects to find some shift
nested inside the reset
. It will then CPS transform the shift
into a ControlContext][A, B, C]
and the code that happens after the shift
into a ControlContext.map
call.
Because you have an if
statement, in the case where the else branch is taken, there is no nested shift
:
reset {
if (false) {
shift { ... }
}
Result(cached(id)) // no shift
}
Same with
reset {
if (false) {
shift { ... }
} else {
Result(cached(id)) // no shift
}
}
That cannot be transformed into valid CPS code.
It seems you could have the reset inside the if branch or supply a trivial shift statement to the else branch:
if (!cached.isDefinedAt(id)) reset {
shift { ... }
Result(cached(id))
} else {
Result(cached(id))
}
// or
reset {
if (!cached.isDefinedAt(id)) {
shift { ... }
Result(cached(id))
} else {
shift[Result[Object], ExecState[Object], ExecState[Object]] { k =>
Result(cached(id))
}
}
}
Edit: It does seems there is some inconsistencies on how the cps plugin infers the types. For example:
var b = false
def test[A](a: A) = reset {
if (b) {
a
} else {
shift{ (k: Unit => A) => k() }
a
}
}
Running compilation with the -Xprint:selectivecps
options shows that the compiler infers the type as Reset[A, Nothing]
then running the code will produce an error at runtime. If the if is reversed as:
var b = false
def test[A](a: A) = reset {
if (b) {
shift{ (k: Unit => A) => k() }
a
} else {
a
}
}
Then the compiler correctly infers reset[A, A]
. If I provide the type parameters to reset
like test[A](a: A) = reset[A, A] {
then it works in both cases.
Maybe specifying the type parameters to reset
and shift
and also instead of using Result(5)
, using the shiftUnit[A, B, C]
method will help with reducing inconsistencies.