effekt icon indicating copy to clipboard operation
effekt copied to clipboard

Change semantics of `return` to be an early return

Open b-studios opened this issue 11 months ago • 7 comments

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).

b-studios avatar Mar 04 '24 13:03 b-studios

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).

jiribenes avatar Jun 18 '24 14:06 jiribenes

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.

b-studios avatar Jun 19 '24 07:06 b-studios

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?

jiribenes avatar Jun 19 '24 08:06 jiribenes

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.

b-studios avatar Jun 19 '24 10:06 b-studios

I had a quick thought: If we use Control[R] as boundary/break, what should the semantics of continue be in the boundary handler?

jiribenes avatar Jun 27 '24 12:06 jiribenes

There are only two options, right? Either it aligns with break, or it restarts :)

b-studios avatar Jun 27 '24 15:06 b-studios

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.

b-studios avatar Jul 29 '24 12:07 b-studios