Skip to content

Instantly share code, notes, and snippets.

@marcinzh
Created May 30, 2025 11:51
Show Gist options
  • Save marcinzh/5e026d3410ae4fd21f1e0da4d5f1e3ab to your computer and use it in GitHub Desktop.
Save marcinzh/5e026d3410ae4fd21f1e0da4d5f1e3ab to your computer and use it in GitHub Desktop.

Revisions

  1. marcinzh created this gist May 30, 2025.
    107 changes: 107 additions & 0 deletions CatchFail_no_shadow.scala
    Original 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