Monads interoperability.¶
Monads in async
and await
can have different types, i.e. await[F]
can be applied inside async[G]
.
We define the trait CpsMonadConversion[F, G]
to support the conversion from F[_]
to G[_]
.
Future
Examples¶
async[F] { await[Future]( /*..*/ ) }
¶
We first describe how to invoke await[Future]
inside a async[F]
block.
Here is an example of implementation of Conversion
from Future
to any async monad G[_]
:
given fromFutureConversion[G[_]](using ExecutionContext, CpsAsyncMonad[G]):
CpsMonadConversion[Future, G] with
def apply[T](ft: Future[T]): G[T] =
summon[CpsAsyncMonad[G]].adoptCallbackStyle(
listener => ft.onComplete(listener) )
Here, ‘async monad’ for G[_]
means it is possible to receive G[T]
from a callback, which returns T
.
trait CpsAsyncMonad[F[?]] extends CpsTryMonad[F] {
/**
* called by the source, which accept callback.
* source is called immediately in adoptCallbackStyle
**/
def adoptCallbackStyle[A](source: (Try[A] => Unit) => Unit): F[A]
}
After making this definition available, we can await Future
inside any async monad:
import scala.concurrent.Future
def fun(x: Int): Future[Int] =
Future successful (x+1)
val c = async[ComputationBound] {
val a = await(fun(10))
a
}
async[Future] { await[F]( /*..*/ ) }
¶
And how about inserting await[F]
into a async[Future]
block ?
For this, it means that our F
should be able to schedule operation:
trait CpsSchedulingMonad[F[?]] extends CpsAsyncMonad[F] {
/**
* schedule execution of op somewhere.
* Note, that characteristics of scheduler may vary.
**/
def spawn[A](op: => F[A]): F[A]
}
This can be immediately evaluated for imperative monads, or for monads with delayed evaluation,
like Haskell-like IO – submitting the op
argument to a pull, which should be evaluated during unsafePerformIO
at the end of the world.
You can read implementation of conversion of scheduled monad to Future
in the source file FutureAsyncMonad.scala
.
Of course, it is possible to create other conversions between your monads, based on other principles.
js.Promise¶
Not only monads can be subject to await. For example, it is impossible to attach the monadic structure to Promise
in Scala.js, because the map operation is unimplementable: all Promise
operations flatten their arguments.
But we can still await Promise
from Scala async[Future]
blocks, because CpsMonadConversion[Future, Promise]
is defined.
Also, for a fluent implementation of JS facades, dotty-cps-async provides the JSFuture
trait, which has monadic operations in Scala and visible from JavaScript as Promise
.
i.e. with the following definitions:
import cps.monads.jsfuture.{given,*}
@JSExportTopLevel("FromScalaExample")
object FromScalaExample:
@JSExport
def myFunction(x: String): JSFuture[String] = async[JSFuture] {
// can use await from futures and promises
// ...
}
FromScalaExample.myFunction("string")
can be used as Promise
on the JavaScript side.
Monad Context Inclusion¶
We also can mix different monads in direct context encoding:
@experimental
object AsyncChannelExample:
type IOResourceDirect = CpsDirect[[X]=>>Resource[IO,X]]
def open(name: Path, options: OpenOption*)(using IOResourceDirect): AsynchronousFileChannel =
...
def read(input: AsynchronousFileChannel, bufSize: Int)(using CpsDirect[IO]): ByteBuffer =
...
def topLevelCall(name:String, data:String): Resorce[IO,Unit] = async[[X]=>>Resource[IO,X]] {
val file = open(name)
val data = read(input, MAX_BUFF_SIZE)
.....
}
As with plain async, we can call operation with monad F[_] in G[_] if given CpsMonadConversion[F, G]
is defined.
Additionally, it is possible to fine-tune the inclusion of monad contexts by defining CpsMonadContextInclusion[F, G]
, which allows us to
pass information into the monad context of the target call.