Created
May 28, 2025 13:38
-
-
Save marcinzh/927fc54b8547ab21f2ce71020d734076 to your computer and use it in GitHub Desktop.
CatchFail.hs translated to Scala 3 and Turbolift
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>` | |
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