Created
May 30, 2025 11:51
-
-
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`)
This 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 characters
//> 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