Skip to content

Instantly share code, notes, and snippets.

@kitlangton
Created March 10, 2024 13:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kitlangton/a0ea44ff9264737b11dd85a9d1effcb3 to your computer and use it in GitHub Desktop.
Save kitlangton/a0ea44ff9264737b11dd85a9d1effcb3 to your computer and use it in GitHub Desktop.
zero.scala
import izumi.reflect.Tag
import <.Handler
opaque type <[+A, -S] = Zero[A, S]
inline implicit def toZero[A, S](v: A): A < S = Zero.Succeed(v)
inline implicit def fromZero[A, S](zero: Zero[A, S]): A < S = zero
object < :
import Zero.*
type MX[A] = Any
extension [A, S](self: A < S) //
def map[B, S2](f: A => B < S2): B < (S & S2) = flatMap(f)
def flatMap[B, S2](k: A => B < S2): B < (S & S2) =
self match
case Succeed(value) => k(value)
case suspend: Suspend[MX, A] @unchecked => FlatMap(suspend, k)
case FlatMap(suspend: Suspend[?, A], f) => FlatMap(suspend, (v: A) => f(v).flatMap(k))
trait Handler[Command[_], Result[_], E](using tag: Tag[E]):
import Zero.*
def pure[A](v: A): Result[A]
def handleOne[A, B, S](command: Command[A], k: A => B < S): B < S
// Example Zero:
// FlatMap(
// Suspend(Get[Int]), // <- GET
// FlatMap(
// Console.readLine
// FlatMap(
// Get[Int],// <- GET
//
// If we're handling the Get, then we must match every Suspended Get
// and process the continuation with the value of the Get
// Of course, if we reach a Suspend that is not a Get, we must pass it through
// to the next handler
def handle[A, B, S](value: A < (E & S)): Result[A] < S =
def handleLoop(value: A < (E & S)): Result[A] < S =
value match
case Return(value) => value.asInstanceOf[Result[A]]
case Succeed(value) => pure(value)
case Suspend(command: Command[A] @unchecked, tag) if tag.tag == tag.tag =>
handleLoop(handleOne(command, v => v))
case flatMap: FlatMap[Command, A, B, Any] @unchecked if flatMap.root.tag.tag == tag.tag =>
handleLoop(handleOne(flatMap.root.command, flatMap.k))
case zero @ Suspend(command: Command[A] @unchecked, tag) =>
zero.map(v => handleLoop(v))
case zero: FlatMap[Command, A, B, Any] @unchecked =>
FlatMap(zero.root, (v: A) => handleLoop(zero.k(v)))
handleLoop(value)
sealed trait Zero[+A, -S]
object Zero:
case class Succeed[A](value: A) extends Zero[A, Any]
case class Suspend[Command[_], A](command: Command[A], tag: Tag[?]) extends Zero[A, Any]
case class FlatMap[Command[_], A, B, S](root: Suspend[Command, A], k: A => B < S) extends Zero[B, Any]
case class Return[A](value: A) extends Zero[Nothing, Any]
enum Console[A]:
case PrintLine(line: String) extends Console[Unit]
case ReadLine extends Console[String]
class Consoles
type Id[A] = A
object Console:
private val tag = Tag[Consoles]
def printLine(line: String): Unit < Consoles = Zero.Suspend(PrintLine(line), tag)
def readLine: String < Consoles = Zero.Suspend(ReadLine, tag)
val handler = new Handler[Console, Id, Consoles]:
def pure[A](v: A): Id[A] = v
def handleOne[A, B, S](command: Console[A], k: A => B < S): B < S =
command match
case Console.PrintLine(line) => k(println(line))
case Console.ReadLine => k(scala.io.StdIn.readLine().asInstanceOf[A])
def fakeHandler(value: String) = new Handler[Console, Id, Consoles]:
def pure[A](v: A): Id[A] = v
def handleOne[A, B, S](command: Console[A], k: A => B < S): B < S =
command match
case Console.PrintLine(line) => k(println(line))
case Console.ReadLine => k(value)
def run[A, S](v: A < (Consoles & S)): A < S =
handler.handle(v)
trait Options:
val tag = Tag[Options]
def get[A](v: Option[A]): A < Options = Zero.Suspend(v, tag)
val handler = new Handler[Option, Option, Options]:
def pure[A](v: A): Option[A] = Some(v)
def handleOne[A, B, S](command: Option[A], k: A => B < S): B < S =
command match
case Some(value) => k(value)
case None => Zero.Return(None)
object Options extends Options
enum Envs[Consoles]:
case Get[A]() extends Envs[A]
object Envs:
def get[A: Tag]: A < Envs[A] = Zero.Suspend(Envs.Get(), Tag[Envs[A]])
def handler[A: Tag](value: A) = new Handler[Envs, Id, Envs[A]]:
def pure[A](v: A): Id[A] = v
def handleOne[A, B, S](command: Envs[A], k: A => B < S): B < S =
command match
case Envs.Get() => k(value.asInstanceOf[A])
def run[E: Tag, A, S](value: E)(v: A < (Envs[E] & S)): A < S =
val handler = Envs.handler(value)
handler.handle(v)
object Example extends App:
val program: String < (Options & Envs[Int] & Consoles) = for
number <- Envs.get[Int]
n2 <- Options.get(Option.empty[Int])
_ <- Console.printLine(s"Hello No. $number, what's your name?")
number2 <- Envs.get[Int]
name <- Console.readLine
_ <- Console.printLine(s"Hello, $name (a.k.a, No. $number)!")
yield "!" * n2
val fakeConsole = Console.fakeHandler("Kit")
println("Program")
println(program)
val result = Envs.run(42)(program)
println("After handling Env")
println(result)
val result2 = fakeConsole.handle(result)
println("After handling Console")
println(result2)
val result3 = Options.handler.handle(result2)
println("After handling Options")
println(result3)
println("OH")
println(s"RESULT: $result3")
val col = Options.get(Option.empty[Int])
val handled = Options.handler.handle(col)
println(s"I HAVE HANDLED $handled")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment