-
-
Save kubukoz/050d0bdc61195467ecce25ec6dd23800 to your computer and use it in GitHub Desktop.
tagless final implementation from noel welsh's talk "tagless final for humans" https://noelwelsh.com/talks/tagless-final-for-humans
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
// https://noelwelsh.com/talks/tagless-final-for-humans | |
trait Algebra: | |
type Ui[_] | |
trait Controls extends Algebra: | |
def text(prompt: String): Ui[String] | |
def choice(prompt: String, options: (String, Boolean)*): Ui[Boolean] | |
// etc... | |
trait Layout extends Algebra: | |
def and[A, B](t: Ui[A], b: Ui[B]): Ui[(A, B)] | |
// Declare a type for programs | |
trait Program[-Alg <: Algebra, A]: | |
// NOTE: delays choice of algebra to after the program is defined | |
def apply( | |
alg: Alg | |
): alg.Ui[A] | |
// Define constructors returning programs | |
object Controls: | |
def text(prompt: String): Program[Controls, String] = _.text(prompt) | |
def choice( | |
prompt: String, | |
options: (String, Boolean)* | |
): Program[Controls, Boolean] = _.choice(prompt, options*) | |
// Define combinators using extension methods | |
extension [Alg <: Algebra, A](p: Program[Alg, A]) | |
def and[Alg2 <: Algebra, B]( | |
second: Program[Alg2, B] | |
): Program[Alg & Alg2 & Layout, (A, B)] = alg => alg.and(p(alg), second(alg)) | |
// Reached our goal | |
val ui = Controls | |
.text("What is your name?") | |
.and( | |
Controls.choice( | |
"Are you enjoying Scalar?", | |
"Yes" -> true, | |
"Heck yes!" -> true, | |
) | |
) |
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 toolkit typelevel:0.1.29 | |
// example interpreter of the program | |
import cats.data.Const | |
import cats.syntax.all.* | |
// convert the UI to a sequence of tokens for pretty printing | |
object Tokenize extends Controls, Layout: | |
// pretend you're an A, but really you're an (accumulating) list of tokens | |
type Ui[A] = Const[List[Token], A] | |
enum Token: | |
case Indent | |
case Undent | |
case Pretty(value: String) | |
extension (tokens: List[Token]) | |
def pretty: String = | |
def go(tokens: List[Token], indent: Int): String = | |
tokens match | |
case Nil => "" | |
case Token.Indent :: next => go(next, indent + 1) | |
case Token.Undent :: next => go(next, indent - 1) | |
case Token.Pretty(value) :: next => (" " * indent) + value + "\n" + go(next, indent) | |
go(tokens, 0) | |
// conjunction is the semigroupal product | |
def and[A, B](a: Ui[A], b: Ui[B]): Ui[(A, B)] = | |
Const.of(List(Token.Pretty("and"), Token.Indent)) *> | |
(a, b).tupled <* | |
Const.of(List(Token.Undent)) | |
def choice(prompt: String, options: (String, Boolean)*): Ui[Boolean] = | |
Const.of(List(Token.Pretty(s"choice: $prompt, ${options.mkString(", ")}"))) | |
def text(prompt: String): Ui[String] = | |
Const.of(List(Token.Pretty(s"text: $prompt"))) | |
@main | |
def main = | |
println(ui(Tokenize).getConst.pretty) | |
/* | |
and | |
text: What is your name? | |
choice: Are you enjoying Scalar?, (Yes,true), (Heck yes!,true) | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment