Replaces SuspendApp with BlockingBlocking executes suspended definitions in sequential order on a single-threaded ExecutionContext, returning the resumed values and blocking until the resumed values are available.
def twoSuspensions(x: Int)(using s:Suspend, ec: ExecutionContext): Int =
val f = s.shift{ c =>
ec.execute{() =>
Thread.sleep(Random.between(1_000, 10_000)
c.resume(x + 5)
}
}
s.shift(_.resume(f + 1)
given executor:ExecutionContextExecutorService =
ExecutionContext.fromExecutorService(Executors.newWorkStealingPool())
println(List(1,2,3,4).map(i => Blocking(twoSuspensions(i))))
// List(7, 8, 9, 10), execution of each suspension happens sequentially.
Adds Deferred Launcher
Deferred launchers launch their suspensions in a non-blocking manner, returning a Deferred[A] object with an await method to block and extract the A value. IterableOnce[Deferred[A]]s also have an awaitAll that will allow concurrent execution of all of the Deferred returns in the IterableOnce, but will block the caller until all the Deferreds have completed and return a List[A]. awaitAll is more efficient than collection.map(i => Deferred(suspendedDef(i)).await().
def twoSuspensions(x: Int)(using s:Suspend, ec: ExecutionContext): Int =
val f = s.shift{ c =>
ec.execute{() =>
Thread.sleep(Random.between(1_000, 10_000)
c.resume(x + 5)
}
}
s.shift(_.resume(f + 1)
given executor:ExecutionContextExecutorService =
ExecutionContext.fromExecutorService(Executors.newWorkStealingPool())
println(List(1,2,3,4).map(i => Deferred(twoSuspensions(i))).awaitAll())
// List(7, 8, 9, 10), execution of each suspension happens concurrently.
Deferred is not full structured concurrency at this time. There is no cancellation.
[ ] Simplifies the transformation to use simple substitution for using Suspend
Previously, the following suspended definition:
def suspendedDefinition(x: Int, y: Int)(z: Int)(using Suspend): Int
would have its curried arguments flattened in order to insert the necessary Continuation[Int] completion parameter:
def suspendedDefinition(x: Int, y: Int, z: Int, completion: Continuation[Int]): Int
This caused some very complicated logic to handle rewrites of applications and the body transformations of the suspended definitions. This necessitated launchers to also be fully transformed, and limited their utility. Now, we simply substitute the completion wherever the contextual Suspend parameter appears in the definition, which is both easier to implement and allows launchers to be defined as library-level code rather than as compiler generated code:
def suspendedDefinition(x: Int, y: Int)(z: Int)(using completion: Continuation[Int]): Int
Applications of suspended definitions must still be transformed to include the nearest completion parameter. Launchers all take a Continuation[A] ?=> A block in their apply methods. Thus, the parameter can be retrieved from the launchers, which do not need to be transformed and can be implemented simply via substitution. This substitution is only necessary because implicit injection occurs during the previous phases of the compiler, and so must be replicated here:
//
Blocking(suspendedDefinition(1, 2)(3))
Becomes (evidence parameter name may differ):
Blocking(suspendedDefinition(1, 2)(ev$1)
This prevents many issues with ownership and proxying due to the previous complex transformations.
[ ] Environment Variable capture is now performed with a deepFold and filter instead of takeWhiles and complex folds.
[ ] State machine generation now operates almost solely as TreeTypeMaps. This makes generating state machine cases a lot less complex. Future improvements could be made to consolidate the mapping into fewer TreeTypeMaps.
[ ] Constant definition names have been extracted into constants.scala. This will allow the transformation to be parameterized in the future.
[ ] Frame class generation has been inlined into the main TreeTypeMap transformation, and names have been clarified. Iterators have been used to generate the names from the captured environment values.
Suspended ValDef declarations no longer work. A follow-up PR will address this issue in a manner similar to the DefDef transformations. ValDef contextual function transformation is quite different from DefDef transformation, due to access to parameters provided by DefDef and symbols with a guaranteed MethodType type.
Replaces
SuspendApp
withBlocking
Blocking
executes suspended definitions in sequential order on a single-threadedExecutionContext
, returning the resumed values and blocking until the resumed values are available.Adds
Deferred
LauncherDeferred
launchers launch their suspensions in a non-blocking manner, returning aDeferred[A]
object with anawait
method to block and extract theA
value.IterableOnce[Deferred[A]]
s also have anawaitAll
that will allow concurrent execution of all of theDeferred
returns in theIterableOnce
, but will block the caller until all theDeferred
s have completed and return aList[A]
.awaitAll
is more efficient thancollection.map(i => Deferred(suspendedDef(i)).await()
.Deferred
is not full structured concurrency at this time. There is no cancellation.[ ] Simplifies the transformation to use simple substitution for
using Suspend
Previously, the following suspended definition:would have its curried arguments flattened in order to insert the necessary
Continuation[Int]
completion parameter:This caused some very complicated logic to handle rewrites of applications and the body transformations of the suspended definitions. This necessitated launchers to also be fully transformed, and limited their utility. Now, we simply substitute the completion wherever the contextual
Suspend
parameter appears in the definition, which is both easier to implement and allows launchers to be defined as library-level code rather than as compiler generated code:Applications of suspended definitions must still be transformed to include the nearest completion parameter. Launchers all take a
Continuation[A] ?=> A
block in their apply methods. Thus, the parameter can be retrieved from the launchers, which do not need to be transformed and can be implemented simply via substitution. This substitution is only necessary because implicit injection occurs during the previous phases of the compiler, and so must be replicated here:Becomes (evidence parameter name may differ):
This prevents many issues with ownership and proxying due to the previous complex transformations.
[ ] Environment Variable capture is now performed with a
deepFold
andfilter
instead of takeWhiles and complexfold
s.[ ] State machine generation now operates almost solely as
TreeTypeMap
s. This makes generating state machine cases a lot less complex. Future improvements could be made to consolidate the mapping into fewerTreeTypeMap
s.[ ] Constant definition names have been extracted into
constants.scala
. This will allow the transformation to be parameterized in the future.[ ] Frame class generation has been inlined into the main
TreeTypeMap
transformation, and names have been clarified.Iterator
s have been used to generate the names from the captured environment values.Suspended
ValDef
declarations no longer work. A follow-up PR will address this issue in a manner similar to theDefDef
transformations.ValDef
contextual function transformation is quite different fromDefDef
transformation, due to access to parameters provided byDefDef
and symbols with a guaranteedMethodType
type.