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.