Skip to content

Instantly share code, notes, and snippets.

@marcinzh
Created May 28, 2025 13:38
Show Gist options
  • Save marcinzh/927fc54b8547ab21f2ce71020d734076 to your computer and use it in GitHub Desktop.
Save marcinzh/927fc54b8547ab21f2ce71020d734076 to your computer and use it in GitHub Desktop.
CatchFail.hs translated to Scala 3 and Turbolift
//> 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>`
Link to the original:
https://gist.github.com/ymdryo/f1a6d4dc0c09fd8ab69e6bc4c25cc34e
**********************************************************************************************/
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 =
// Not having anything like `interpose(SingleOperation)`, I'm resorting to defining a whole handler.
// It trivially passes all operations to the outer handler, except for `catchAllEff`.
// BTW, the `Control.shadow` feature is in experimental state, but it manages to handle our case.
new fx.impl.Stateless[Identity, Identity, Console & fx.type] with fx.impl.Sequential with ErrorSignature[String, String]:
override def onReturn(a: Unknown) = a.pure_!!
override def raise(e: String): Nothing !! ThisEffect = Control.shadow(fx.raise(e))
override def raises(e: String): Nothing !! ThisEffect = Control.shadow(fx.raises(e))
override def toEither[A, U <: ThisEffect](comp: A !! U) = Control.shadow(fx.toEither(comp))
override def catchAllEff[A, U <: ThisEffect](body: A !! U)(f: String => A !! U): A !! U =
Control.shadow:
fx.catchAllEff(
Console.println("[LOG] Entering catch scope...") &&!
body
)(e =>
Console.println(s"[LOG] Caught exception: $e") &&!
f(e)
)
.toHandler
.handle(comp)
/***************************************************************
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 value-parameters if needed.
All code BELOW uses 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 & MyError & 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