Created
March 10, 2024 13:55
-
-
Save kitlangton/a0ea44ff9264737b11dd85a9d1effcb3 to your computer and use it in GitHub Desktop.
zero.scala
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
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