Created
          May 30, 2025 11:51 
        
      - 
      
- 
        Save marcinzh/5e026d3410ae4fd21f1e0da4d5f1e3ab to your computer and use it in GitHub Desktop. 
Revisions
- 
        marcinzh created this gist May 30, 2025 .There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,107 @@ //> 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 <link-to-this-gist>` **********************************************/ 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