Skip to content

Instantly share code, notes, and snippets.

@lagenorhynque
Last active April 6, 2026 10:15
Show Gist options
  • Select an option

  • Save lagenorhynque/43e4e290aad01a11be28c998fb35dee8 to your computer and use it in GitHub Desktop.

Select an option

Save lagenorhynque/43e4e290aad01a11be28c998fb35dee8 to your computer and use it in GitHub Desktop.
Tagless final pattern in Flix
// expressions as a type class
trait Expr[repr: Type -> Type] {
pub def int(n: Int32): repr[Int32]
pub def bool(b: Bool): repr[Bool]
pub def add(e1: repr[Int32], e2: repr[Int32]): repr[Int32]
pub def mul(e1: repr[Int32], e2: repr[Int32]): repr[Int32]
pub def eq(e1: repr[a], e2: repr[a]): repr[Bool] with Eq[a]
}
// an evaluator
enum Eval[a](a)
def runEval(x: Eval[a]): a = match x {
case Eval.Eval(value) => value
}
// an implementation of the expression language as an evaluator
instance Expr[Eval] {
pub def int(n: Int32): Eval[Int32] = Eval.Eval(n)
pub def bool(b: Bool): Eval[Bool] = Eval.Eval(b)
pub def add(e1: Eval[Int32], e2: Eval[Int32]): Eval[Int32] = match (e1, e2) {
case (Eval.Eval(x), Eval.Eval(y)) => Eval.Eval(x + y)
}
pub def mul(e1: Eval[Int32], e2: Eval[Int32]): Eval[Int32] = match (e1, e2) {
case (Eval.Eval(x), Eval.Eval(y)) => Eval.Eval(x * y)
}
pub def eq(e1: Eval[a], e2: Eval[a]): Eval[Bool] with Eq[a] = match (e1, e2) {
case (Eval.Eval(x), Eval.Eval(y)) => Eval.Eval(x == y)
}
}
// a pretty printer
enum Pretty[_a](String)
def runPretty(x: Pretty[a]): String = match x {
case Pretty.Pretty(s) => s
}
// an implementation of the expression language as a pretty printer
instance Expr[Pretty] {
pub def int(n: Int32): Pretty[Int32] = Pretty.Pretty("${n}")
pub def bool(b: Bool): Pretty[Bool] = Pretty.Pretty("${b}")
pub def add(e1: Pretty[Int32], e2: Pretty[Int32]): Pretty[Int32] = match (e1, e2) {
case (Pretty.Pretty(s1), Pretty.Pretty(s2)) => Pretty.Pretty("(${s1} + ${s2})")
}
pub def mul(e1: Pretty[Int32], e2: Pretty[Int32]): Pretty[Int32] = match (e1, e2) {
case (Pretty.Pretty(s1), Pretty.Pretty(s2)) => Pretty.Pretty("(${s1} * ${s2})")
}
pub def eq(e1: Pretty[a], e2: Pretty[a]): Pretty[Bool] with Eq[a] = match (e1, e2) {
case (Pretty.Pretty(s1), Pretty.Pretty(s2)) => Pretty.Pretty("(${s1} == ${s2})")
}
}
// examples of using the expression language with both interpreters
def evalExample(): Unit \ IO =
use Expr.{int, add, mul, eq};
let e1 = add(int(1), mul(int(2), int(3)));
let e2 = eq(e1, int(7));
println("e1 evaluates to ${runEval(e1)}");
println("e2 evaluates to ${runEval(e2)}")
def prettyExample(): Unit \ IO =
use Expr.{int, add, mul, eq};
let e1 = add(int(1), mul(int(2), int(3)));
let e2 = eq(e1, int(7));
println("e1 pretty prints to ${runPretty(e1)}");
println("e2 pretty prints to ${runPretty(e2)}")
// using generic functions to avoid repeating the same code for both interpreters
def exp1(): repr[Int32] with Expr[repr] =
use Expr.{int, add, mul};
add(int(1), mul(int(2), int(3)))
def exp2(): repr[Bool] with Expr[repr] =
use Expr.{int, eq};
eq(exp1(), int(7))
def evalExample2(): Unit \ IO =
println("e1 evaluates to ${runEval(exp1())}");
println("e2 evaluates to ${runEval(exp2())}")
def prettyExample2(): Unit \ IO =
println("e1 pretty prints to ${runPretty(exp1())}");
println("e2 pretty prints to ${runPretty(exp2())}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment