//> using scala "3.3.5" //> using dep "io.github.marcinzh::turbolift-core:0.112.0" //> using dep "io.github.marcinzh::turbolift-bindless:0.112.0" /************* HOW TO RUN ******************** 1. Ensure you have Java 11 or newer 2. Install scala-cli 3. Execute `scala-cli ` **********************************************/ import turbolift.{!!, Signature, Effect, Handler} import turbolift.effects.{Console, IO, Error, ErrorSignature} import turbolift.Extensions._ import turbolift.bindless._ trait ChoiceSignature extends Signature: def fail(reason: String): Nothing !! ThisEffect def choose: Boolean !! ThisEffect trait ChoiceEffect extends Effect[ChoiceSignature] with ChoiceSignature: final override def fail(reason: String): Nothing !! ThisEffect = perform(_.fail(reason)) final override def choose: Boolean !! ThisEffect = perform(_.choose) extension (fx: ChoiceEffect) def handler_NoThrowOnFail = new fx.impl.Stateless[Identity, Vector, Any] with fx.impl.Sequential with ChoiceSignature: override def onReturn(a: Unknown) = Vector(a).pure_!! override def fail(reason: String): Nothing !! ThisEffect = Control.abort(Vector()) override def choose = Control.capture(k => k(false).zipWith(k(true))(_ ++ _)) .toHandler // Takes an instance of `Error` effect as a dependency def handler_ThrowOnFail(errorFx: Error[String]): Handler[Identity, Vector, fx.type, errorFx.type] = new fx.impl.Stateless[Identity, Vector, errorFx.type] with fx.impl.Sequential with ChoiceSignature: override def onReturn(a: Unknown) = Vector(a).pure_!! override def fail(reason: String): Nothing !! ThisEffect = errorFx.raise(reason) override def choose = Control.capture(k => k(false).zipWith(k(true))(_ ++ _)) .toHandler extension (fx: Error[String]) def hookLoggingToCatch[A, U <: Console & fx.type](comp: A !! U): A !! U = // Mostly a copy & paste of the original handler for `Error` effect new fx.impl.Stateless[Identity, [X] =>> Either[String, X], Console] with fx.impl.Sequential with ErrorSignature[String, String]: override def onReturn(a: Unknown) = Right(a).pure_!! override def raise(e: String) = Control.abort(Left(e)) override def raises(e: String) = Control.abort(Left(e)) override def toEither[A, U <: ThisEffect](comp: A !! U) = Control.delimit(comp) override def catchAllEff[A, U <: ThisEffect](body: A !! U)(f: String => A !! U): A !! U = toEither( Console.println("[LOG] Entering catch scope...") &&! body ).flatMap: case Right(a) => a.pure_!! case Left(e) => Console.println(s"[LOG] Caught exception: $e") &&! f(e) .toHandler .handle(comp) .flatMap(fx.fromEither) /*************************************************************** In Turbolift effects are not only types, but also values. See `Labelled Effects` at https://marcinzh.github.io/turbolift/advanced/labelled.html All code ABOVE is effect-instance-independent, taking them as parameters if required. All code BELOW depends on concrete effect instances. ***************************************************************/ case object MyError extends Error[String] case object MyChoice extends ChoiceEffect type MyError = MyError.type type MyChoice = MyChoice.type def failWhenBothTrue: (Boolean, Boolean) !! (MyChoice & Console) = `do`: val x = MyChoice.choose.! val y = MyChoice.choose.! if x && y then MyChoice.fail("x = y = True").! Console.println(s"x=$x y=$y").! (x, y) def catchFail: Option[(Boolean, Boolean)] !! (MyChoice & MyError & Console) = MyError.catchAllEff(failWhenBothTrue.map(Some(_))): reason => Console.println(s"caught failure: $reason") &&! None.pure_!! def program: Option[(Boolean, Boolean)] !! (MyChoice & MyError & Console) = MyError.hookLoggingToCatch(catchFail) @main def main = `do`: Console.println("[NoThrowOnFail]").! val r1 = program.handleWith(MyChoice.handler_NoThrowOnFail).handleWith(MyError.handler).! Console.println(r1.toString).! Console.println("").! Console.println("[ThrowOnFail]").! val r2 = program.handleWith(MyChoice.handler_ThrowOnFail(MyError)).handleWith(MyError.handler).! Console.println(r2.toString).! .handleWith(Console.handler) .runIO