kyo.Combinators.scala Maven / Gradle / Ivy
The newest version!
package kyo
import kyo.debug.Debug
import kyo.kernel.Boundary
import kyo.kernel.Reducible
import scala.annotation.tailrec
import scala.annotation.targetName
import scala.reflect.ClassTag
extension [A, S](effect: A < S)
/** Performs this computation and then the next one, discarding the result of this computation.
*
* @param next
* The computation to perform after this one
* @return
* A computation that produces the result of `next`
*/
@targetName("zipRight")
def *>[A1, S1](next: => A1 < S1)(using Frame): A1 < (S & S1) =
effect.map(_ => next)
/** Performs this computation and then the next one, discarding the result of the next computation.
*
* @param next
* The computation to perform after this one
* @return
* A computation that produces the result of this computation
*/
@targetName("zipLeft")
def <*[A1, S1](next: => A1 < S1)(using Frame): A < (S & S1) =
effect.map(e => next.as(e))
/** Performs this computation and then the next one, returning both results as a tuple.
*
* @param next
* The computation to perform after this one
* @return
* A computation that produces a tuple of both results
*/
@targetName("zip")
def <*>[A1, S1](next: => A1 < S1)(using Frame): (A, A1) < (S & S1) =
effect.map(e => next.map(n => (e, n)))
/** Performs this computation, discards its result, and returns the given value.
*
* @param value
* The value to return
* @return
* A computation that produces the given value
*/
inline def as[A1, S1](value: => A1 < S1)(using inline frame: Frame): A1 < (S & S1) =
effect.map(_ => value)
/** Performs this computation and prints its result to the console.
*
* @return
* A computation that produces the result of this computation
*/
def debugValue(using Frame): A < S = Debug(effect)
/** Performs this computation and prints its result to the console with a detailed execution trace.
*
* @return
* A computation that produces the result of this computation
*/
def debugTrace(using Frame): A < S = Debug.trace(effect)
/** Performs this computation after a delay.
*
* @param duration
* The duration to delay for
* @return
* A computation that produces the result of this computation with Async effect
*/
def delayed[S1](duration: Duration < S1)(using Frame): A < (S & S1 & Async) =
duration.map(d => Async.delay(d)(effect))
/** Performs this computation repeatedly with a backoff policy.
*
* @param policy
* The backoff policy to use
* @return
* A computation that produces the result of the last execution
*/
def repeat(policy: Retry.Policy)(using Flat[A], Frame): A < (S & Async) =
Loop.indexed { i =>
if i >= policy.limit then effect.map(Loop.done)
else effect.delayed(policy.backoff(i)).as(Loop.continue)
}
/** Performs this computation repeatedly with a limit.
*
* @param limit
* The maximum number of times to repeat the computation
* @return
* A computation that produces the result of the last execution
*/
def repeat[S1](limit: Int < S1)(using Flat[A], Frame): A < (S & S1) =
limit.map { limit =>
Loop.indexed { i =>
if i >= limit then effect.map(Loop.done)
else effect.as(Loop.continue)
}
}
/** Performs this computation repeatedly with a backoff policy and a limit.
*
* @param backoff
* The backoff policy to use
* @param limit
* The limit to use
* @return
* A computation that produces the result of this computation with Async effect
*/
def repeat[S1](backoff: Int => Duration, limit: => Int < S1)(using
Flat[A],
Frame
): A < (S & S1 & Async) =
limit.map { limit =>
Loop.indexed { i =>
if i >= limit then effect.map(Loop.done)
else effect.delayed(backoff(i)).as(Loop.continue)
}
}
end repeat
/** Performs this computation repeatedly while the given condition holds.
*
* @param fn
* The condition to check after each iteration
* @return
* A computation that produces the result of the last successful execution before the condition becomes false
*/
def repeatWhile[S1](fn: A => Boolean < S1)(using Flat[A], Frame): A < (S & S1 & Async) =
def loop(last: A): A < (S & S1 & Async) =
fn(last).map { cont =>
if cont then effect.map(v => loop(v))
else last
}
effect.map(v => loop(v))
end repeatWhile
/** Performs this computation repeatedly while the given condition holds.
*
* @param fn
* The condition to check after each iteration taking the current value and the number of iterations so far, and returning a tuple
* with the condition and the duration to sleep between iterations
* @return
* A computation that produces the result of the last successful execution before the condition becomes false
*/
def repeatWhile[S1](fn: (A, Int) => (Boolean, Duration) < S1)(using
Flat[A],
Frame
): A < (S & S1 & Async) =
def loop(last: A, i: Int): A < (S & S1 & Async) =
fn(last, i).map { case (cont, duration) =>
if cont then effect.delayed(duration).map(v => loop(v, i + 1))
else last
}
effect.map(v => loop(v, 0))
end repeatWhile
/** Performs this computation repeatedly until the given condition holds.
*
* @param fn
* The condition to check after each iteration
* @return
* A computation that produces the result of the first execution where the condition becomes true
*/
def repeatUntil[S1](fn: A => Boolean < S1)(using Flat[A], Frame): A < (S & S1 & Async) =
def loop(last: A): A < (S & S1 & Async) =
fn(last).map { cond =>
if cond then last
else effect.map(loop)
}
effect.map(v => loop(v))
end repeatUntil
/** Performs this computation repeatedly until the given condition holds.
*
* @param fn
* The condition to check after each iteration taking the current value and the number of iterations so far, and returning a tuple
* with the condition and the duration to sleep between iterations
* @return
* A computation that produces the result of the first execution where the condition becomes true
*/
def repeatUntil[S1](fn: (A, Int) => (Boolean, Duration) < S1)(using
Flat[A],
Frame
): A < (S & S1 & Async) =
def loop(last: A, i: Int): A < (S & S1 & Async) =
fn(last, i).map { case (cont, duration) =>
if cont then last
else Kyo.sleep(duration) *> effect.map(v => loop(v, i + 1))
}
effect.map(v => loop(v, 0))
end repeatUntil
/** Performs this computation repeatedly with a retry policy in case of failures.
*
* @param policy
* The retry policy to use
* @return
* A computation that produces the result of this computation with Async and Abort[Throwable] effects
*/
def retry(policy: Retry.Policy)(using Flat[A], Frame): A < (S & Async & Abort[Throwable]) =
Retry[Throwable](policy)(effect)
/** Performs this computation repeatedly with a limit in case of failures.
*
* @param limit
* The limit to use
* @return
* A computation that produces the result of this computation with Async and Abort[Throwable] effects
*/
def retry[S1](n: => Int < S1)(using Flat[A], Frame): A < (S & S1 & Async & Abort[Throwable]) =
n.map(nPure => Retry[Throwable](Retry.Policy(_ => Duration.Zero, nPure))(effect))
/** Performs this computation repeatedly with a backoff policy and a limit in case of failures.
*
* @param backoff
* The backoff policy to use
* @param limit
* The limit to use
* @return
* A computation that produces the result of this computation with Async and Abort[Throwable] effects
*/
def retry[S1](backoff: Int => Duration, n: => Int < S1)(using
Flat[A],
Frame
): A < (S & S1 & Async & Abort[Throwable]) =
n.map(nPure => Retry[Throwable](Retry.Policy(backoff, nPure))(effect))
/** Performs this computation indefinitely.
*
* @return
* A computation that never completes
* @note
* This method is typically used for long-running processes or servers that should run continuously
*/
def forever(using Frame): Nothing < S =
(effect *> effect.forever) *> (throw new IllegalStateException("infinite loop ended"))
/** Performs this computation when the given condition holds.
*
* @param condition
* The condition to check
* @return
* A computation that produces the result of this computation with Abort[Maybe.Empty] effect
*/
def when[S1](condition: => Boolean < S1)(using Frame): A < (S & S1 & Abort[Maybe.Empty]) =
condition.map(c => if c then effect else Abort.fail(Maybe.Empty))
/** Performs this computation catching any Throwable in an Abort[Throwable] effect.
*
* @return
* A computation that produces the result of this computation with Abort[Throwable] effect
*/
def explicitThrowable(using Flat[A], Frame): A < (S & Abort[Throwable]) =
Abort.catching[Throwable](effect)
/** Performs this computation and applies an effectful function to its result.
*
* @return
* A computation that produces the result of this computation
*/
def tap[S1](f: A => Any < S1)(using Frame): A < (S & S1) =
effect.map(a => f(a).as(a))
/** Performs this computation unless the given condition holds, in which case it returns an Abort[Maybe.Empty] effect.
*
* @param condition
* The condition to check
* @return
* A computation that produces the result of this computation with Abort[Maybe.Empty] effect
*/
def unless[S1](condition: Boolean < S1)(using Frame): A < (S & S1 & Abort[Maybe.Empty]) =
condition.map(c => if c then Abort.fail(Maybe.Empty) else effect)
end extension
extension [A, S, E](effect: A < (Abort[E] & S))
/** Handles the Abort effect and returns its result as a `Result[E, A]`.
*
* @return
* A computation that produces the result of this computation with the Abort[E] effect handled
*/
def handleAbort(
using
ct: ClassTag[E],
tag: Tag[E],
flat: Flat[A]
)(using Frame): Result[E, A] < S =
Abort.run[E](effect)
def handleSomeAbort[E1 <: E]: HandleSomeAbort[A, S, E, E1] = HandleSomeAbort(effect)
/** Translates the Abort effect to a Choice effect.
*
* @return
* A computation that produces the result of this computation with the Abort[E] effect translated to Choice
*/
def abortToChoice(
using
ct: ClassTag[E],
tag: Tag[E],
flat: Flat[A]
)(using Frame): A < (S & Choice) =
effect.handleAbort.map(e => Choice.get(e.fold(_ => Nil)(List(_))))
def someAbortToChoice[E1 <: E](using Frame): SomeAbortToChoiceOps[A, S, E, E1] = SomeAbortToChoiceOps(effect)
/** Translates the Abort[E] effect to an Abort[Maybe.Empty] effect in case of failure.
*
* @return
* A computation that produces the result of this computation with the Abort[E] effect translated to Abort[Maybe.Empty]
*/
def abortToEmpty(
using
ct: ClassTag[E],
tag: Tag[E],
flat: Flat[A]
)(using Frame): A < (S & Abort[Maybe.Empty]) =
effect.handleAbort.map {
case Result.Fail(_) => Abort.fail(Maybe.Empty)
case Result.Panic(e) => throw e
case Result.Success(a) => a
}
def someAbortToEmpty[E1 <: E]: SomeAbortToEmptyOps[A, S, E, E1] = SomeAbortToEmptyOps(effect)
/** Handles the Abort effect and applies a recovery function to the error.
*
* @return
* A computation that produces the result of this computation with the Abort[E] effect handled
*/
def catchAbort[A1 >: A, S1](fn: E => A1 < S1)(
using
ct: ClassTag[E],
tag: Tag[E],
fl: Flat[A]
)(using Frame): A1 < (S & S1) =
effect.handleAbort.map {
case Result.Fail(e) => fn(e)
case Result.Panic(e) => throw e
case Result.Success(v) => v
}
/** Handles the Abort effect and applies a partial recovery function to the error.
*
* @return
* A computation that produces the result of this computation with Abort[E] effect
*/
def catchAbortPartial[A1 >: A, S1](fn: PartialFunction[E, A1 < S1])(
using
ct: ClassTag[E],
tag: Tag[E],
fl: Flat[A],
frame: Frame
): A1 < (S & S1 & Abort[E]) =
effect.handleAbort.map {
case Result.Fail(e) =>
if fn.isDefinedAt(e) then fn(e)
else Abort.fail(e)
case Result.Panic(e) => throw e
case Result.Success(v) => v
}
def catchSomeAbort[E1 <: E](using Frame): CatchSomeAbort[A, S, E, E1] = CatchSomeAbort(effect)
def catchSomeAbortPartial[E1 <: E](using Frame): CatchSomeAbortPartialOps[A, S, E, E1] = CatchSomeAbortPartialOps(effect)
/** Translates the Abort effect by swapping the error and success types.
*
* @return
* A computation that produces the failure E as result of this computation and the success A as Abort[A] effect
*/
def swapAbort(
using
cta: ClassTag[A],
cte: ClassTag[E],
te: Tag[E],
fl: Flat[A],
frame: Frame
): E < (S & Abort[A]) =
val handled: Result[E, A] < S = effect.handleAbort
handled.map((v: Result[E, A]) => Abort.get(v.swap))
end swapAbort
def swapSomeAbort[E1 <: E](using Frame): SwapSomeAbortOps[A, S, E, E1] = SwapSomeAbortOps(effect)
/** Catches any Throwable in an Abort[Throwable] effect.
*
* @return
* A computation that produces the result of this computation with Abort[Throwable] effect
*/
def implicitThrowable[ER](
using
ev: E => Throwable | ER,
f: Flat[A],
reduce: Reducible[Abort[ER]],
frame: Frame
): A < (S & reduce.SReduced) =
Abort.run[Throwable](effect.asInstanceOf[A < (Abort[Throwable | ER] & S)])
.map(_.fold(e => throw e.getFailure)(identity))
end extension
extension [A, S, E](effect: A < (Abort[Maybe.Empty] & S))
/** Handles the Abort[Maybe.Empty] effect and returns its result as a `Maybe[A]`.
*
* @return
* A computation that produces the result of this computation with the Abort[Maybe.Empty] effect handled
*/
def handleEmptyAbort(using f: Flat[A], frame: Frame): Maybe[A] < S =
Abort.run[Maybe.Empty](effect).map {
case Result.Fail(_) => Maybe.Empty
case Result.Panic(e) => throw e
case Result.Success(a) => Maybe.Defined(a)
}
/** Translates the Abort[Maybe.Empty] effect to a Choice effect.
*
* @return
* A computation that produces the result of this computation with the Abort[Maybe.Empty] effect translated to Choice
*/
def emptyAbortToChoice(using f: Flat[A], frame: Frame): A < (S & Choice) =
effect.someAbortToChoice[Maybe.Empty]()
/** Handles the Abort[Maybe.Empty] effect translating it to an Abort[E] effect.
*
* @return
* A computation that produces the result of this computation with the Abort[Maybe.Empty] effect translated to Abort[E]
*/
def emptyAbortToFailure[S1](failure: => E < S1)(using f: Flat[A], frame: Frame): A < (S & S1 & Abort[E]) =
for
f <- failure
res <- effect.handleSomeAbort[Maybe.Empty]()
yield res match
case Result.Fail(_) => Abort.get(Result.Fail(f))
case Result.Panic(e) => Abort.get(Result.Panic(e))
case Result.Success(a) => Abort.get(Result.success(a))
end extension
class SomeAbortToChoiceOps[A, S, E, E1 <: E](effect: A < (Abort[E] & S)) extends AnyVal:
/** Handles the Abort[E] effect and returns its result as a `Choice`.
*
* @return
* A computation that produces the result of this computation with Choice effect
*/
def apply[ER]()(
using
ev: E => E1 | ER,
ct: ClassTag[E1],
tag: Tag[E1],
reduce: Reducible[Abort[ER]],
flat: Flat[A],
frame: Frame
): A < (S & reduce.SReduced & Choice) =
Abort.run[E1](effect.asInstanceOf[A < (Abort[E1 | ER] & S)]).map(e => Choice.get(e.fold(_ => Nil)(List(_))))
end SomeAbortToChoiceOps
class SomeAbortToEmptyOps[A, S, E, E1 <: E](effect: A < (Abort[E] & S)) extends AnyVal:
/** Handles the Abort[E] effect translating it to an Abort[Maybe.Empty] effect.
*
* @return
* A computation that produces the result of this computation with Abort[Maybe.Empty] effect
*/
def apply[ER]()(
using
ev: E => E1 | ER,
ct: ClassTag[E1],
tag: Tag[E1],
reduce: Reducible[Abort[ER]],
flat: Flat[A],
frame: Frame
): A < (S & reduce.SReduced & Abort[Maybe.Empty]) =
Abort.run[E1](effect.asInstanceOf[A < (Abort[E1 | ER] & S)]).map {
case Result.Fail(_) => Abort.get(Result.Fail(Maybe.Empty))
case p @ Result.Panic(e) => Abort.get(p.asInstanceOf[Result[Nothing, Nothing]])
case s @ Result.Success(a) => Abort.get(s.asInstanceOf[Result[Nothing, A]])
}
end SomeAbortToEmptyOps
class HandleSomeAbort[A, S, E, E1 <: E](effect: A < (Abort[E] & S)) extends AnyVal:
/** Handles the Abort[E] effect and returns its result as a `Result[E, A]`.
*
* @return
* A computation that produces the a `Result[E, A]`
*/
def apply[ER]()(
using
ev: E => E1 | ER,
ct: ClassTag[E1],
tag: Tag[E1],
reduce: Reducible[Abort[ER]],
flat: Flat[A],
frame: Frame
): Result[E1, A] < (S & reduce.SReduced) =
Abort.run[E1](effect.asInstanceOf[A < (Abort[E1 | ER] & S)])
end HandleSomeAbort
class CatchSomeAbort[A, S, E, E1 <: E](effect: A < (Abort[E] & S)) extends AnyVal:
/** Handles the Abort[E] effect and applies a recovery function to the error.
*
* @return
* A function that takes a recovery function and returns a computation that produces the result of this computation with the Abort[E]
* effect handled
*/
def apply[ER]()(
using
ev: E => E1 | ER,
reduce: Reducible[Abort[ER]],
ct: ClassTag[E1],
tag: Tag[E1],
f: Flat[A],
frame: Frame
): [A1 >: A, S1] => (E1 => A1 < S1) => A1 < (S & S1 & reduce.SReduced) =
[A1 >: A, S1] =>
(fn: E1 => A1 < S1) =>
reduce(Abort.run[E1](effect.asInstanceOf[A < (Abort[E1 | ER] & S)]).map {
case Result.Fail(e1) => fn(e1)
case Result.Success(v) => v
case Result.Panic(ex) => throw ex
})
end CatchSomeAbort
class CatchSomeAbortPartialOps[A, S, E, E1 <: E](effect: A < (Abort[E] & S)) extends AnyVal:
/** Handles the Abort[E] effect and applies a partial recovery function to the error.
*
* @return
* A function that takes a partial recovery function and returns a computation that produces the result of this computation with the
* Abort[E] effect handled
*/
def apply[ER]()(
using
ev: E => E1 | ER,
ct: ClassTag[E1],
tag: Tag[E1],
f: Flat[A],
frame: Frame
): [A1 >: A, S1] => PartialFunction[E1, A1 < S1] => A1 < (S & S1 & Abort[E]) =
[A1 >: A, S1] =>
(fn: PartialFunction[E1, A1 < S1]) =>
Abort.run[E1](effect).map {
case Result.Fail(e1) if fn.isDefinedAt(e1) => fn(e1)
case e1: Result.Error[?] => Abort.get(e1)
case Result.Success(a) => a
}
end apply
end CatchSomeAbortPartialOps
class SwapSomeAbortOps[A, S, E, E1 <: E](effect: A < (Abort[E] & S)) extends AnyVal:
/** Translates the Abort[E] effect to an Abort[A] effect.
*
* @return
* A computation that produces the result of this computation with the Abort[E] effect translated to Abort[A]
*/
def apply[ER]()(
using
ev: E => E1 | ER,
reduce: Reducible[Abort[ER]],
ct: ClassTag[E1],
tag: Tag[E1],
f: Flat[A],
frame: Frame
): E1 < (S & reduce.SReduced & Abort[A]) =
val handled = Abort.run[E1](effect.asInstanceOf[A < (Abort[E1 | ER] & S)])
handled.map((v: Result[E1, A]) => Abort.get(v.swap))
end apply
end SwapSomeAbortOps
extension [A, S, E](effect: A < (S & Env[E]))
/** Handles the Env[E] efffect with the provided value.
*
* @param dependency
* The value to provide for the environment
* @return
* A computation that produces the result of this computation with the Env[E] effect handled
*/
def provideValue[S1, E1 >: E, ER](dependency: E1 < S1)(
using
ev: E => E1 & ER,
flat: Flat[A],
reduce: Reducible[Env[ER]],
tag: Tag[E1],
frame: Frame
): A < (S & S1 & reduce.SReduced) =
dependency.map(d => Env.run[E1, A, S, ER](d)(effect.asInstanceOf[A < (S & Env[E1 | ER])]))
/** Handles the Env[E] effect with the provided layer.
*
* @param layer
* The layers to perform this computation with
* @return
* A computation that produces the result of this computation
*/
inline def provideLayer[S1, S2, E1 >: E, ER](layer: Layer[E1, S1] < S2)(
using
ev: E => E1 & ER,
flat: Flat[A],
reduce: Reducible[Env[ER]],
tag: Tag[E1],
frame: Frame
): A < (S & S1 & S2 & Memo & reduce.SReduced) =
for
l <- layer
tm <- l.run
e1 = tm.get[E1]
yield effect.provideValue(e1)
/** Handles the Env[E] effect with the provided layers via Env.runLayer.
*
* @param layers
* The layers to handle this computation with
* @return
* A computation that produces the result of this computation
*/
transparent inline def provide(inline layers: Layer[?, ?]*): A < Nothing =
Env.runLayer(layers*)(effect)
end extension
extension [A, S](effect: A < (S & Choice))
/** Filters the result of this computation and performs a `Choice` effect if the condition does not hold.
*
* @return
* A computation that produces the result of this computation with Choice effect
*/
def filterChoice[S1](fn: A => Boolean < S1)(using Frame): A < (S & S1 & Choice) =
effect.map(a => fn(a).map(b => Choice.dropIf(!b)).as(a))
/** Handles the Choice effect and returns its result as a `Seq[A]`.
*
* @return
* A computation that produces a sequence of results from this computation with the Choice effect handled
*/
def handleChoice(using Flat[A], Frame): Seq[A] < S = Choice.run(effect)
/** Translates the Choice effect to an Abort[E] effect in case the result is empty.
*
* @return
* A computation that produces the result of this computation with Abort[E] effect
*/
def choiceToAbort[E, S1](error: => E < S1)(
using
Flat[A],
Frame
): A < (S & S1 & Abort[E]) =
Choice.run(effect).map {
case s if s.isEmpty => Kyo.fail[E, S1](error)
case s => s.head
}
/** Translates the Choice effect to an Abort[Throwable] effect.
*
* @return
* A computation that produces the result of this computation with Abort[Throwable] effect
*/
def choiceToThrowable(using Flat[A], Frame): A < (S & Abort[Throwable]) =
choiceToAbort[Throwable, Any](new NoSuchElementException("head of empty list"))
/** Translates the Choice effect to an Abort[Maybe.Empty] effect.
*
* @return
* A computation that produces the result of this computation with Abort[Maybe.Empty] effect
*/
def choiceToEmpty(using Flat[A], Frame): A < (S & Abort[Maybe.Empty]) =
choiceToAbort(Maybe.Empty)
end extension
extension [A, E, Ctx](effect: A < (Abort[E] & Async & Ctx))
/** Forks this computation using the Async effect and returns its result as a `Fiber[E, A]`.
*
* @return
* A computation that produces the result of this computation with Async effect
*/
def fork(
using
flat: Flat[A],
boundary: Boundary[Ctx, IO],
reduce: Reducible[Abort[E]],
frame: Frame
): Fiber[E, A] < (IO & Ctx) =
Async.run(effect)(using flat, boundary)
/** Forks this computation using the Async effect and returns its result as a `Fiber[E, A]`, managed by the Resource effect. Unlike
* `fork`, which creates an unmanaged fiber, `forkScoped` ensures that the fiber is properly cleaned up when the enclosing scope is
* closed, preventing resource leaks.
*
* @return
* A computation that produces the result of this computation with Async and Resource effects
*/
def forkScoped(
using
flat: Flat[A],
boundary: Boundary[Ctx, IO],
reduce: Reducible[Abort[E]],
frame: Frame
): Fiber[E, A] < (IO & Ctx & Resource) =
Kyo.acquireRelease(Async.run(effect))(_.interrupt.unit)
end extension
extension [A, E, S](fiber: Fiber[E, A] < S)
/** Joins the fiber and returns its result as a `A`.
*
* @return
* A computation that produces the result of this computation with Async effect
*/
def join(using reduce: Reducible[Abort[E]], frame: Frame): A < (S & reduce.SReduced & Async) =
fiber.map(_.get)
/** Awaits the completion of the fiber and returns its result as a `Unit`.
*
* @return
* A computation that produces the result of this computation with Async effect
*/
def awaitCompletion(using Flat[A], Frame): Unit < (S & Async) =
fiber.map(_.getResult.unit)
end extension
extension [A, E, Ctx](effect: A < (Abort[E] & Async & Ctx))
/** Performs this computation and then the next one in parallel, discarding the result of this computation.
*
* @param next
* The computation to perform after this one
* @return
* A computation that produces the result of `next`
*/
@targetName("zipRightPar")
def &>[A1, E1, Ctx1](next: A1 < (Abort[E1] & Async & Ctx1))(
using
f: Flat[A],
f1: Flat[A1],
b: Boundary[Ctx, IO],
b1: Boundary[Ctx1, IO],
r: Reducible[Abort[E]],
r1: Reducible[Abort[E1]],
fr: Frame
): A1 < (r.SReduced & r1.SReduced & Async & Ctx & Ctx1) =
for
fiberA <- effect.fork
fiberA1 <- next.fork
_ <- fiberA.awaitCompletion
a1 <- fiberA1.join
yield a1
/** Performs this computation and then the next one in parallel, discarding the result of the next computation.
*
* @param next
* The computation to perform after this one
* @return
* A computation that produces the result of this computation
*/
@targetName("zipLeftPar")
def <&[A1, E1, Ctx1](next: A1 < (Abort[E1] & Async & Ctx1))(
using
f: Flat[A],
f1: Flat[A1],
b: Boundary[Ctx, IO],
b1: Boundary[Ctx1, IO],
r: Reducible[Abort[E]],
r1: Reducible[Abort[E1]],
fr: Frame
): A < (r.SReduced & r1.SReduced & Async & Ctx & Ctx1) =
for
fiberA <- effect.fork
fiberA1 <- next.fork
a <- fiberA.join
_ <- fiberA1.awaitCompletion
yield a
/** Performs this computation and then the next one in parallel, returning both results as a tuple.
*
* @param next
* The computation to perform after this one
* @return
* A computation that produces a tuple of both results
*/
@targetName("zipPar")
def <&>[A1, E1, Ctx1](next: A1 < (Abort[E1] & Async & Ctx1))(
using
f: Flat[A],
f1: Flat[A1],
b: Boundary[Ctx, IO],
b1: Boundary[Ctx1, IO],
r: Reducible[Abort[E]],
r1: Reducible[Abort[E1]],
fr: Frame
): (A, A1) < (r.SReduced & r1.SReduced & Async & Ctx & Ctx1) =
for
fiberA <- effect.fork
fiberA1 <- next.fork
a <- fiberA.join
a1 <- fiberA1.join
yield (a, a1)
end extension