Skip to content

Conversation

marzipankaiser
Copy link
Contributor

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 marzipankaiser added feature New feature or request area:stdlib labels May 13, 2025
@b-studios
Copy link
Collaborator

The question is whether do continue still works, no?

@jiribenes
Copy link
Contributor

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

@marzipankaiser
Copy link
Contributor Author

marzipankaiser commented May 13, 2025

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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

@marzipankaiser
Copy link
Contributor Author

@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
Copy link
Contributor Author

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?

@jiribenes
Copy link
Contributor

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 #404

@marzipankaiser
Copy link
Contributor Author

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

@b-studios
Copy link
Collaborator

I restarted the job since it might be flaky.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:stdlib feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants