effekt
effekt copied to clipboard
Change semantics of `return` to be an early return
Right now return
is a keyword but only used to embed expressions into statements. This is fine for a core representation like System C, but not appropriate for source.
That is, right now
def foo() = {
val x = if (true) return 42 else 0;
x + 1
}
currently returns 43
and not 42
(which might be confusing to people).
Counter proposal: we could also forbid return
everywhere (or at least in the "early return" position) and instead force the users to use boundary
/break
for early returns (like in Scala 3).
yes, main point is that it is very confusing the way it is now. For an IR return
is fine, for the source language, the way it currently is, is not.
Currently Control
as in the stdlib is not good enough for boundary/break, since it does not take arguments. I guess we should change this, then.
Agreed, parametrising Control
sounds good [1. changing Control
to Control[R]
; 2. changing the signature of break
; 3. adding some general boundary
handler], the biggest problem I can foresee is the following:
What should be the type of foreach
?
https://github.com/effekt-lang/effekt/blob/1f0c055f69c0596098e7b135ddc0e408cb08efbb/libraries/common/list.effekt#L117
// Option A: we can only use `do break(())`, i.e. `Control[Unit]`
// ... but what if I _want_ to return some value?
def foreach[A](l: List[A]) { f: A => Unit / Control[Unit] } : Unit
// Option B: we align the type of `break` with the return type
// ... but what if I do _not_ want to return a value? Perhaps this needs to return `Option[R]`?
// ... also we might have errors/warnings because we're ignoring a returned value?
def foreach[A, R](l: List[A]) { f: A => Unit / Control[R] } : R
// which is strictly more general than Option A and reduces to it if we break with `()`
Are there any other possibilities / designs?
It needs to be Control[Unit]
since in the case of not returning early we would otherwise need to make up an R
.
Actually, same for map
, where we IIRC use Control
to break and continue, not to abort mapping altogether with a different value.
I had a quick thought: If we use Control[R]
as boundary/break
, what should the semantics of continue
be in the boundary
handler?
There are only two options, right? Either it aligns with break, or it restarts :)
I just tried adding an argument to break
, but it is really annoying in the cases, where Control[Unit]
and we would like to call label.break()
; here we need to write label.break(())
, then.