All Downloads are FREE. Search and download functionalities are using the official Maven repository.

turbolift.Computation.scala Maven / Gradle / Ivy

The newest version!
package turbolift
import turbolift.effects.{ChoiceSignature, IO, Each, Finalizer}
import turbolift.internals.auxx.CanPartiallyHandle
import turbolift.internals.effect.AnyChoice
import turbolift.internals.executor.Executor
import turbolift.interpreter.Prompt
import turbolift.internals.engine.{Env, Engine, Tag}
import turbolift.io.{Outcome, Fiber, Warp}
import turbolift.mode.Mode
import turbolift.{ComputationCases => CC}


/** Alias for [[Computation]] type. Meant to be used in infix form. */
type !![+A, -U] = Computation[A, U]

/** Alias for [[Computation]] companion object. */
def !! = Computation


/** Monad parametrized by a set of requested effect. Use the `!!` infix type alias instead.
 *   
 * Type-level set of effects is modelled with intersection types. 
 * Type `Any` means empty set.
 *
 * {{{
 * type MyComputationType1 = String !! (MyState & MyError)
 * type MyComputationType2 = String !! Any
 * }}}
 *
 * @tparam A Result of the computation
 * @tparam U Type-level set of effects requested by this computation.
 */

sealed abstract class Computation[+A, -U] private[turbolift] (private[turbolift] val tag: Tag):
  private final def map_bug[B](f: A => B): B !! U = map(f)

  final inline def map[B](inline f: A => B): B !! U =
    new CC.Map[A, B, B, U](Tag.PureMap, this):
      override def apply(a: A): B = f(a)

  final inline def flatMap[B, U2 <: U](inline f: A => B !! U2): B !! U2 =
    new CC.Map[A, B, B !! U2, U2](Tag.FlatMap, this):
      override def apply(a: A): B !! U2 = f(a)

  final def flatten[B, U2 <: U](implicit ev: A <:< (B !! U2)): B !! U2 = flatMap(ev)
  @deprecated final def flatTap[B, U2 <: U](f: A => B !! U2): A !! U2 = tapEff(f)
  final def tapEff[B, U2 <: U](f: A => B !! U2): A !! U2 = flatMap(a => f(a).as(a))

  /** Composes 2 independent computations sequentially */
  final def zip[B, U2 <: U](that: => B !! U2): (A, B) !! U2 = flatMap(a => that.map_bug((a, _)))

  /** Composes 2 independent computations parallelly (if possible).
   *
   * Parallelism may be impossible, due to at least one of handlers in current scope
   * being inherently sequential (e.g. `State.handlers.local` or `Error.handlers.first`).
   * In such case, `zipPar` behaves like `zip`.
   */
  final def zipPar[B, U2 <: U](that: B !! U2): (A, B) !! U2 = zipWithPar(that)(Computation.pairCtorFun[A, B])

  /** Like [[zip]], but followed by untupled `map`. */
  final def zipWith[B, C, U2 <: U](that: => B !! U2)(f: (A, B) => C): C !! U2 = flatMap(a => that.map(f(a, _)))

  /** Like [[zipPar]], but followed by untupled `map`. */
  final def zipWithPar[B, C, U2 <: U](that: B !! U2)(f: (A, B) => C): C !! U2 = CC.intrinsic(_.intrinsicZipPar(this, that, f))
  
  /** Discards the result, and replaces it by given pure value. */
  final def as[B](value: B): B !! U = map(_ => value)

  /** Discards the result, and replaces it by `Unit`. */
  final def void: Unit !! U = as(())

  /** Alias for [[flatMap]]. */
  final def >>=[B, U2 <: U](f: A => B !! U2): B !! U2 = flatMap(f)

  /** Alias for [[zipPar]]. */
  final def *![B, U2 <: U](that: B !! U2): (A, B) !! U2 = zipPar(that)

  /** Alias for [[zip]]. */
  final def **![B, U2 <: U](that: => B !! U2): (A, B) !! U2 = zip(that)

  /** Composes 2 independent computations parallelly (if possible), discarding result of the first.
   *
   * Parallelism may be impossible, due to at least one of handlers in current scope
   * being inherently sequential (e.g. `State.handlers.local` or `Error.handlers.first`).
   * In such case, `&!` behaves like `&&!`.
   */
  final def &![B, U2 <: U](that: B !! U2): B !! U2 = zipPar(that).map_bug(_._2)
  
  /** Composes 2 independent computations sequentially, discarding result of the first. */
  final def &&![B, U2 <: U](that: => B !! U2): B !! U2 = flatMap(_ => that)

  /** Composes 2 independent computations parallelly (if possible), discarding result of the second.
   *
   * Parallelism may be impossible, due to at least one of handlers in current scope
   * being inherently sequential (e.g. `State.handlers.local` or `Error.handlers.first`).
   * In such case, `& B !! U2): A !! U2 = flatMap(a => that.as(a))

  /** Races 2 computations.
   *
   * Runs both computations parallelly, each in fresh fiber.
   * Once one of them finishes, the other is cancelled.
   */
  final def |![A2 >: A, U2 <: U & IO](that: A2 !! U2): A2 !! U2 = CC.intrinsic(_.intrinsicOrPar(this, that))

  /** Sequential "or-else" operator.
   *
   * Runs the first computations in fresh fiber.
   * If it ends up cancelled, the second computation is run.
   */
  final def ||![A2 >: A, U2 <: U & IO](that: => A2 !! U2): A2 !! U2 = CC.intrinsic(_.intrinsicOrSeq(this, () => that))

  /** Parallel version of `++!`. */
  final def +![A2 >: A, U2 <: U & ChoiceSignature](that: A2 !! U2): A2 !! U2 = AnyChoice.plusPar(this, that)

  /** Applies `plus` operation from the innermost `Choice` effect in the current scope.
   *
   * Similar to `<|>` operator of `Alternative`.
   */
  final def ++![A2 >: A, U2 <: U & ChoiceSignature](that: => A2 !! U2): A2 !! U2 = AnyChoice.plus(this, that)

  /** Applies filter, using `empty` operation from the innermost `Choice` effect in the current scope. */
  final def withFilter[U2 <: U & ChoiceSignature](f: A => Boolean): A !! U2 = flatMap(a => if f(a) then !!.pure(a) else !!.empty)

  /** Widens the set of requested effects. */
  final def upCast[U2 <: U] = this: A !! U2

  final def cast[A2, U2]: A2 !! U2 = asInstanceOf[Computation[A2, U2]]

  private[turbolift] final def untyped = this.asInstanceOf[Any !! Any]

  //@#@OLD
  // final override def toString = s"turbolift.Computation@${hashCode.toHexString}"

  //---------- IO operations in postfix syntax ----------

  /** Run this computation in a new fiber. */
  final def fork: Fiber[A, U] !! (IO & Warp) = Fiber.fork(this)

  /** Like [[fork]], but the fiber is created as a child of specific warp, rather than the current warp. */
  final def forkAt(warp: Warp): Fiber[A, U] !! IO = Fiber.forkAt(warp)(this)

  final def onFailure[U2 <: U & IO](f: Throwable => Unit !! U2): A !! U2 = IO.onFailure(this)(f)

  final def onCancel[U2 <: U & IO](comp: Unit !! U2): A !! U2 = IO.onCancel(this)(comp)

  final def guarantee[U2 <: U & IO](release: Unit !! U2): A !! U2 = IO.guarantee(release)(this)

  final def executeOn[U2 <: U & IO](exec: Executor): A !! U2 = IO.executeOn(exec)(this)

  /** Syntax for giving names to fibers. */
  final def named[A2 >: A, U2 <: U](name: String) = new Computation.NamedSyntax[A2, U2](this, name)


/**
  * Use the `!!` alias to access methods of this companion object.
  * 
  * Example:
  * {{{
  * import turbolift.!!
  * 
  * val myComputation: Int !! Any = !!.pure(42)
  * }}}
  */


object Computation:
  private[turbolift] type Untyped = Computation[Any, Any]

  private def pairCtorFun[A, B]: (A, B) => (A, B) = pairCtorVal.asInstanceOf[(A, B) => (A, B)]
  private val pairCtorVal: (Any, Any) => Any = (_, _) 

  /** Same as `!!.pure(())`. */
  val unit: Unit !! Any = pure(())

  /** Same as `!!.pure(None)`. */
  val none: Option[Nothing] !! Any = pure(None)

  /** Same as `!!.pure(Nil)`. */
  val nil: List[Nothing] !! Any = pure(Nil)

  /** Same as `!!.pure(Vector())`. */
  val vector: Vector[Nothing] !! Any = pure(Vector())

  def pure[A](a: A): A !! Any = new CC.Pure(a)

  inline def impure[A](inline a: => A): A !! Any =
    new CC.Impure[A]:
      override def apply(): A = a

  def impureEff[A, U](comp: => A !! U): A !! U = unit.flatMap(_ => comp)
  @deprecated def defer[A, U](comp: => A !! U): A !! U = impureEff(comp)

  /** Executes `empty` operation from the innermost `Choice` effect in the current scope. */
  def empty: Nothing !! ChoiceSignature = AnyChoice.empty

  /** Handles `Each` effect. */
  def every[A, U](body: A !! (U & Each)): Vector[A] !! U = body.handleWith(Each.handler)

  /** Handles `Each` effect, discarding the result. */
  def everyVoid[A, U](body: Unit !! (U & Each)): Unit !! U = body.handleWith(Each.void)

  /** Like `if`-`then` statement, but the body is a computation */
  def when[U](cond: Boolean)(body: => Unit !! U): Unit !! U = if cond then body else unit

  /** Repeats the computation, given number of times. */
  def repeat[U](n: Int)(body: => Unit !! U): Unit !! U =
    def loop(n: Int): Unit !! U =
      if n > 0 then
        body &&! loop(n - 1)
      else
        unit
    loop(n)

  /** Repeats the computation, while the effectful condition is true. */
  def repeatWhile[U, U2 <: U](cond: Boolean !! U)(body: => Unit !! U2): Unit !! U2 =
    def loop: Unit !! U2 = cond.flatMap(if _ then body &&! loop else unit)
    loop

  /** Repeats the computation, while the effectful condition is false. */
  def repeatUntil[U, U2 <: U](cond: Boolean !! U)(body: => Unit !! U2): Unit !! U2 =
    def loop: Unit !! U2 = cond.flatMap(if _ then unit else body &&! loop)
    loop

  /** Like [[repeat]], but returns results of each iteration. */
  def replicate[A, U](n: Int)(body: => A !! U): Vector[A] !! U =
    def loop(n: Int, acc: Vector[A]): Vector[A] !! U =
      if n > 0 then
        body.flatMap(a => loop(n - 1, acc :+ a))
      else
        pure(acc)
    loop(n, Vector())

  /** Like [[iterate]], but returns results of each iteration. */
  def generate[A, B, U](init: A, cond: A => Boolean, inc: A => A)(body: A => B !! U): Vector[B] !! U =
    def loop(a: A, acc: Vector[B]): Vector[B] !! U =
      if cond(a) then
        body(a).flatMap(b => loop(inc(a), acc :+ b))
      else
        pure(acc)
    loop(init, Vector())

  /** Like the standard `for` loop, but using computation instead of statement.
   *
   *  Returns last value of the iterated variable.
   */
  def iterate[A, U](init: A, cond: A => Boolean, inc: A => A)(body: A => Unit !! U): A !! U =
    def loop(a: A): A !! U =
      if cond(a) then
        body(a) &&! loop(inc(a))
      else
        pure(a)
    loop(init)

  /** Like the standard `for` loop, but using computation instead of statement.
   *
   *  Returns last value of the iterated variable.
   */
  def iterateWhile[A, U](init: A, cond: A => Boolean)(body: A => A !! U): A !! U =
    def loop(a: A): A !! U =
      if cond(a) then
        body(a) >>= loop
      else
        pure(a)
    loop(init)

  /** Like the standard `for` loop, but using computation instead of statement.
   *
   *  Returns last value of the iterated variable.
   */
  def iterateUntil[A, U](init: A, cond: A => Boolean)(body: A => A !! U): A !! U =
    iterateWhile(init, a => !cond(a))(body)

  def envAsk[A](f: Env => A): A !! Any = CC.intrinsic(_.intrinsicEnvAsk(f))
  def envMod[A, U](f: Env => Env, body: A !! U): A !! U = CC.intrinsic(_.intrinsicEnvMod(f, body))

  def isParallel: Boolean !! Any = envAsk(_.isParallelismRequested)
  def isSequential: Boolean !! Any = isParallel.map(!_)
  def parallellyIf[A, U](cond: Boolean)(body: A !! U): A !! U = envMod(_.par(cond), body)
  def sequentiallyIf[A, U](cond: Boolean)(body: A !! U): A !! U = parallellyIf(!cond)(body)
  def parallelly[A, U](body: A !! U): A !! U = parallellyIf(true)(body)
  def sequentially[A, U](body: A !! U): A !! U = parallellyIf(false)(body)


  //---------- Extensions ----------


  extension [A](thiz: Computation[A, Any])
    /** Runs the computation, provided that it requests no effects. */
    def run(using mode: Mode = Mode.default): A = Executor.pick(mode).runSync(thiz, "").get

    def runST: A = Executor.ST.runSync(thiz, "").get
    def runMT: A = Executor.MT.runSync(thiz, "").get


  extension [A](thiz: Computation[A, IO])
    /** Runs the computation, provided that it requests IO effect only, or none at all. */
    def runIO(using mode: Mode = Mode.default): Outcome[A] = Executor.pick(mode).runSync(thiz, "")

    def runAsync(using mode: Mode = Mode.default)(callback: Outcome[A] => Unit): Unit = Executor.pick(mode).runAsync(thiz, "", callback)

    def runIOST: Outcome[A] = Executor.ST.runSync(thiz, "")
    def runIOMT: Outcome[A] = Executor.MT.runSync(thiz, "")

    /*@deprecated*/ def unsafeRun(using mode: Mode = Mode.default): Outcome[A] = runIO
    /*@deprecated*/ def unsafeRunAsync(using mode: Mode = Mode.default)(callback: Outcome[A] => Unit): Unit = runAsync(callback)
    /*@deprecated*/ def unsafeRunST: Outcome[A] = Executor.ST.runSync(thiz, "")
    /*@deprecated*/ def unsafeRunMT: Outcome[A] = Executor.MT.runSync(thiz, "")


  extension [A, U <: IO](thiz: Computation[A, U & Warp])
    def warp: A !! U = thiz.warpCancel
    def warp(exitMode: Warp.ExitMode, name: String = ""): A !! U = thiz.handleWith(Warp.handler(exitMode, name))
    def warpCancel: A !! U = warpCancel("")
    def warpAwait: A !! U = warpAwait("")
    def warpCancel(name: String): A !! U = warp(Warp.ExitMode.Cancel, name)
    def warpAwait(name: String): A !! U = warp(Warp.ExitMode.Await, name)


  extension [A, U <: IO](thiz: Computation[A, U & Finalizer])
    def finalized: A !! U = Finalizer.scoped(thiz)


  extension [A, U](thiz: Computation[A, U])
    def downCast[U2 >: U] = thiz.asInstanceOf[Computation[A, U2]]
  
    /** Simplifies effectful creation of handlers.
     * 
     *  Passes computed value to handler constructor.
     *  Effect used to compute the value, are absorbed by the handler, into its own dependencies.
     */ 
    @annotation.targetName("flatHandleId")
    def >>=![F[+_], L, N](f: A => Handler[[X] =>> X, F, L, N]): Handler[[X] =>> X, F, L, U & N] = Handler.flatHandle(thiz.map(f))

    /** Simplifies effectful creation of handlers.
     * 
     *  Passes computed value to handler constructor.
     *  Effect used to compute the value, are absorbed by the handler, into its own dependencies.
     */ 
    @annotation.targetName("flatHandleConst")
    def >>=![F[+_], L, N](f: A => Handler[[X] =>> A, F, L, N]): Handler[[X] =>> A, F, L, U & N] = Handler.flatHandle(thiz.map(f))

    /** Applies a handler to this computation.
     *
     *  Same as `myHandler.handle(this)`.
     */
    def handleWith[V]: HandleWithSyntax[A, U, V] = new HandleWithSyntax[A, U, V](thiz)


  extension [F[+_], G[+_], L, N](thiz: Computation[Handler[F, G, L, N], N])
    /** Simplifies effectful creation of handlers.
     * 
     *  Same as [[turbolift.Handler.flatHandle Handler.flatHandle(this)]].
     */
    def flattenHandler: Handler[F, G, L, N] = Handler.flatHandle(thiz)


  extension [A, B, U](thiz: Computation[(A, B), U])
    def map2[C](f: (A, B) => C): C !! U = thiz.map(f.tupled)
    def flatMap2[C, U2 <: U](f: (A, B) => C !! U2): C !! U2 = thiz.flatMap(f.tupled)


  extension [U](thiz: Computation[Boolean, U])
    /** Like `while` statement, but the condition and the body are computations. */
    def whileEff[U2 <: U](body: => Unit !! U2): Unit !! U2 = !!.repeatWhile(thiz)(body)

    /** Like `while` statement, but the condition and the body are computations. */
    def untilEff[U2 <: U](body: => Unit !! U2): Unit !! U2 = !!.repeatUntil(thiz)(body)

    /** Like `if` statement, but the condition and the body are computations. */
    def ifEff[U2 <: U](thenBody: => Unit !! U2)(elseBody: => Unit !! U2): Unit !! U2 =
      thiz.flatMap(if _ then thenBody else elseBody)


  //---------- Syntax ----------


  final class HandleWithSyntax[A, U, V](thiz: A !! U):
    def id[F[+_], L, N, V2 <: V & N](h: Handler[[X] =>> X, F, L, N])(implicit ev: CanPartiallyHandle[V, U, L]): F[A] !! V2 =
      h.doHandle[A, V](ev(thiz))

    def const[F[+_], L, N, V2 <: V & N](h: Handler[[X] =>> A, F, L, N])(implicit ev: CanPartiallyHandle[V, U, L]): F[A] !! V2 =
      h.doHandle[A, V](ev(thiz))

    @annotation.targetName("applyId")
    def apply[F[+_], L, N, V2 <: V & N](h: Handler[[X] =>> X, F, L, N])(implicit ev: CanPartiallyHandle[V, U, L]): F[A] !! V2 =
      id(h)

    @annotation.targetName("applyConst")
    def apply[F[+_], L, N, V2 <: V & N](h: Handler[[X] =>> A, F, L, N])(implicit ev: CanPartiallyHandle[V, U, L]): F[A] !! V2 =
      const(h)


  final class NamedSyntax[A, U](val comp: Computation[A, U], val name: String):
    def fork: Fiber[A, U] !! (IO & Warp) = Fiber.named(name).fork(comp)
    def forkAt(warp: Warp): Fiber[A, U] !! IO = Fiber.named(name).forkAt(warp)(comp)
    def run(using mode: Mode = Mode.default, ev: Any <:< U): Outcome[A] = Executor.pick(mode).runSync(comp, name)

  object NamedSyntax:
    extension [A](thiz: NamedSyntax[A, IO])
      def runIO(using mode: Mode = Mode.default): Outcome[A] = Executor.pick(mode).runSync(thiz.comp, thiz.name)
      def runAsync(using mode: Mode = Mode.default)(callback: Outcome[A] => Unit): Unit = Executor.pick(mode).runAsync(thiz.comp,thiz. name, callback)


//@#@TEMP public bcoz inline bug
// private[turbolift] object ComputationCases:
object ComputationCases:
  private[turbolift] final class Pure[A](val value: A) extends Computation[A, Any](Tag.Pure)
  private[turbolift] final class LocalGet(val prompt: Prompt) extends Computation[Any, Any](Tag.LocalGet)
  private[turbolift] final class LocalPut[S](val prompt: Prompt, val local: S) extends Computation[Unit, Any](Tag.LocalPut)
  //@#@TEMP public bcoz inline bug
  sealed abstract class Map[A, B, C, U](_tag: Tag, val comp: A !! U) extends Computation[B, U](_tag) with Function1[A, C]
  sealed abstract class Impure[A]() extends Computation[A, Any](Tag.Impure) with Function0[A]
  sealed abstract class Sync[A, B](val isAttempt: Boolean) extends Computation[B, IO](Tag.Sync) with Function0[A]
  sealed abstract class Intrinsic[A, U] extends Computation[A, U](Tag.Intrinsic) with Function[Engine, Tag]
  sealed abstract class Perform[A, U, Z <: Signature](val sig: Signature) extends Computation[A, U](Tag.Perform) with Function1[Z, A !! U]
  sealed abstract class LocalUpdate[A, S](val prompt: Prompt) extends Computation[A, Any](Tag.LocalUpdate) with Function1[S, (A, S)]

  private[turbolift] inline def sync[A, B](isAttempt: Boolean, inline thunk: => A): B !! IO =
    new Sync[A, B](isAttempt):
      override def apply(): A = thunk

  private[turbolift] inline def intrinsic[A, U](f: Engine => Tag): A !! U =
    new CC.Intrinsic[A, U]:
      override def apply(engine: Engine): Tag = f(engine)

  private[turbolift] inline def perform[A, U, Z <: Signature](sig: Signature, inline f: Z => A !! U): A !! U =
    new Perform[A, U, Z](sig):
      override def apply(z: Z): A !! U = f(z)

  private[turbolift] inline def localUpdate[A, S](prompt: Prompt, inline f: S => (A, S)): A !! Any =
    new LocalUpdate[A, S](prompt):
      override def apply(s: S): (A, S) = f(s)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy