Skip to content

Add suspend strawman #17164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions tests/pos/suspend-strawman/choices.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import scala.util.boundary, boundary.Label
import runtime.suspend

trait Choice:
def choose[A](choices: A*): A

// the handler
def choices[T](body: Choice ?=> T): Seq[T] =
boundary[Seq[T]]:
given Choice with
def choose[A](choices: A*): A =
suspend[A, Seq[T]](s => choices.flatMap(s.resume))
Seq(body)

def choose[A](choices: A*)(using c: Choice): A = c.choose(choices*)

def TestChoices: Seq[Int] =
choices:
def x = choose(1, -2, -3)
def y = choose("ab", "cde")
val xx = x;
xx + (
if xx > 0 then
val z = choose(xx / 2, xx * 2)
y.length * z
else y.length
)

67 changes: 67 additions & 0 deletions tests/pos/suspend-strawman/generators.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import scala.util.boundary
import scala.compiletime.uninitialized
import runtime.suspend

trait CanProduce[-T]:
def produce(x: T): Unit

object generate:

def produce[T](x: T)(using cp: CanProduce[T]): Unit = cp.produce(x)

def apply[T](body: CanProduce[T] ?=> Unit): Iterator[T] = new:
var nextKnown: Boolean = false
var nextElem: Option[T] = uninitialized

var step: () => Unit = () =>
boundary[Unit]:
given CanProduce[T] with
def produce(x: T): Unit =
nextElem = Some(x)
suspend[Unit, Unit]: k =>
step = () => k.resume(())
body
nextElem = None

def hasNext: Boolean =
if !nextKnown then { step(); nextKnown = true }
nextElem.isDefined

def next: T =
require(hasNext)
nextKnown = false
nextElem.get
end generate

enum Tree[T]:
case Leaf(x: T)
case Inner(xs: List[Tree[T]])

def leafs[T](t: Tree[T]): Iterator[T] =
generate:
def recur(t: Tree[T]): Unit = t match
case Tree.Leaf(x) => generate.produce(x)
case Tree.Inner(xs) => xs.foreach(recur)
recur(t)

object Variant2:
trait Generator[T]:
def nextOption: Option[T]

def generate[T](body: CanProduce[T] ?=> Unit): Generator[T] = new:

def nextOption: Option[T] =
step()

var step: () => Option[T] = () =>
boundary:
given CanProduce[T] with
def produce(x: T): Unit =
suspend[Unit, Option[T]]: k =>
step = () => k.resume(())
Some(x)
body
None
end generate


56 changes: 56 additions & 0 deletions tests/pos/suspend-strawman/monadic-reflect.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import scala.util.boundary
import runtime.suspend

trait Monad[F[_]]:

/** The unit value for a monad */
def pure[A](x: A): F[A]

extension [A](x: F[A])
/** The fundamental composition operation */
def flatMap[B](f: A => F[B]): F[B]

/** The `map` operation can now be defined in terms of `flatMap` */
def map[B](f: A => B) = x.flatMap(f.andThen(pure))

end Monad

trait CanReflect[M[_]]:
def reflect[R](mr: M[R]): R

trait Monadic[M[_]: Monad]:

/**
* Embedding of pure values into the monad M
*/
def pure[A](a: A): M[A]

/**
* Sequencing of monadic values
*
* Implementations are required to implement sequencing in a stack-safe
* way, that is they either need to implement trampolining on their own
* or implement `sequence` as a tail recursive function.
*
* Actually the type X can be different for every call to f...
* It is a type aligned sequence, but for simplicity we do not enforce this
* here.
*/
def sequence[X, R](init: M[X])(f: X => Either[M[X], M[R]]): M[R]

/**
* Helper to summon and use an instance of CanReflect[M]
*/
def reflect[R](mr: M[R])(using r: CanReflect[M]): R = r.reflect(mr)

/**
* Reify a computation into a monadic value
*/
def reify[R](prog: CanReflect[M] ?=> R): M[R] =
boundary [M[R]]:
given CanReflect[M] with
def reflect[R2](mr: M[R2]): R2 =
suspend [R2, M[R]] (k => mr.flatMap(k.resume))
pure(prog)

end Monadic
11 changes: 11 additions & 0 deletions tests/pos/suspend-strawman/runtime.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package runtime
import scala.util.boundary, boundary.Label

/** A hypothetical API for suspensions. Not yet implemented.
* Suspension contain a delimited contination, which can be
* invoked with `resume`
*/
class Suspension[-T, +R]:
def resume(arg: T): R = ???

def suspend[T, R](body: Suspension[T, R] => R)(using Label[R]): T = ???
53 changes: 53 additions & 0 deletions tests/pos/suspend-strawman/simple-futures.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package simpleFutures

import scala.collection.mutable.ListBuffer
import scala.util.boundary, boundary.Label
import runtime.suspend

object Scheduler:
def schedule(task: Runnable): Unit = ???

trait Async:
def await[T](f: Future[T]): T

class Future[+T](body: Async ?=> T):
private var result: Option[T] = None
private var waiting: ListBuffer[T => Unit] = ListBuffer()
private def addWaiting(k: T => Unit): Unit = waiting += k

def await(using a: Async): T = a.await(this)

private def complete(): Unit =
Future.async:
val value = body
val result = Some(value)
for k <- waiting do
Scheduler.schedule(() => k(value))
waiting.clear()

Scheduler.schedule(() => complete())

object Future:

// a handler for Async
def async(body: Async ?=> Unit): Unit =
boundary [Unit]:
given Async with
def await[T](f: Future[T]): T = f.result match
case Some(x) => x
case None => suspend[T, Unit](s => f.addWaiting(s.resume))
body

end Future

def Test(x: Future[Int], xs: List[Future[Int]]) =
Future:
x.await + xs.map(_.await).sum