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.
CatchFail.hs translated to Scala 3 and Turbolift (revision without use of `Control.shadow`)
//> 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment