effekt icon indicating copy to clipboard operation
effekt copied to clipboard

Add variant of `loop` / `Control` with a return value

Open marzipankaiser opened this issue 7 months ago • 11 comments

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

marzipankaiser avatar May 13 '25 09:05 marzipankaiser

The question is whether do continue still works, no?

b-studios avatar May 13 '25 09:05 b-studios

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

jiribenes avatar May 13 '25 09:05 jiribenes

The question is whether do continue still works, no?

Surprisingly, it seems to (see nobreak test) for l.continue. I don't think we ever use Control as an effect implicitly (with do), do we?

marzipankaiser avatar May 13 '25 09:05 marzipankaiser

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

marzipankaiser avatar May 13 '25 10:05 marzipankaiser

(Mostly, I just wrote some code and missed something like this, so I tried to add it to stdlib directly.)

marzipankaiser avatar May 13 '25 10:05 marzipankaiser

@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 😄

marzipankaiser avatar May 13 '25 10:05 marzipankaiser

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?

marzipankaiser avatar May 13 '25 10:05 marzipankaiser

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

We discussed this before in https://github.com/effekt-lang/effekt/issues/404

jiribenes avatar May 13 '25 10:05 jiribenes

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

marzipankaiser avatar May 13 '25 11:05 marzipankaiser

I restarted the job since it might be flaky.

b-studios avatar May 13 '25 11:05 b-studios

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

phischu avatar Oct 27 '25 14:10 phischu