Add variant of `loop` / `Control` with a return value
This adds a variant of loop with a return value.
interface ControlAt[T] {
def break(v: T): Unit
def continue(): Unit
}
def loop{ body: {ControlAt[T]} => Unit }: T
Interestingly, overload resultion is able to resolve this, even when we do not use break. 🤔
The question is whether do continue still works, no?
FWIW, something relevant I tend to use somewhat often in my code is:
effect breakWith[A](value: A): Nothing
def boundary[A] { body: => A / breakWith[A] }: A =
try body() with breakWith[A] { a => a }
def boundaryDefault[A] { body: => Unit / breakWith[A] } { default: => A } : A =
try { body(); default() } with breakWith[A] { a => a }
The problem is that the semantics of continue are then confusing when talking about boundary...
A feature that could help accommodate all is "grouping" capabilities together with sub-effecting:
effect Control[A] = { breakAt[A], break, continue }
def loop { body: {Control[A]} => Unit }: A // here we'd have "one" capability only to take care of
// but
def boundary { body: => A / breakAt[A] }: A
The question is whether
do continuestill works, no?
Surprisingly, it seems to (see nobreak test) l.continueControl as an effect implicitly (with do), do we?
Yes, another alternative (since we only use Control explicitly) would be something like:
interface Control[T] {
def break(with: T): Unit
def continue(): Unit
}
def break(c: Control[Unit]): Unit = c.break(())
(Mostly, I just wrote some code and missed something like this, so I tried to add it to stdlib directly.)
@jiribenes Yes, I don't know what is the best solution generally for a boundary-style construct here, though, since often we will then have to - in the implementation or at the call site - decide what to do when there is no value returned.
The nice thing with loop is that this can't happen 😄
A feature that could help accommodate all is "grouping" capabilities together with sub-effecting:
effect Control[A] = { breakAt[A], break, continue } def loop { body: {Control[A]} => Unit }: A // here we'd have "one" capability only to take care of // but def boundary { body: => A / breakAt[A] }: A
Does this work (passing this kind of capability explicitly)? How could we even construct it?
Yes, another alternative (since we only use
Controlexplicitly) would be something like:interface Control[T] { def break(with: T): Unit def continue(): Unit } def break(c: Control[Unit]): Unit = c.break(())
We discussed this before in https://github.com/effekt-lang/effekt/issues/404
The CI failure is weird. acme.effekt is failing on LLVM (only) with a Cannot find value type for b.
https://github.com/effekt-lang/effekt/actions/runs/14993696365/job/42122801169#step:4:462
I restarted the job since it might be flaky.
I want to express interest in this again, ideally in the style of https://github.com/effekt-lang/effekt/issues/404#issuecomment-2454139037
effect break[T](value: T): Unit
def boundary[T] { prog: => Unit / break[T] }: Option[T] =
try {
prog(); None()
} with break[T] { value =>
Some(value)
}