object Tagless extends App {
trait Sym[F[_]] {
def lit[A]: (String, A) => F[A]
def app[A, B]: F[A => B] => F[A] => F[B]
def lam[A, B]: String => (F[A] => F[B]) => F[A => B]
}
object Sym {
def apply[F[_] : Sym] = implicitly[Sym[F]]
def lit[F[_] : Sym, A](s: String, a: A) =
Sym[F].lit(s, a)
def lit[F[_] : Sym, A](a: A) =
Sym[F].lit(a.toString, a)
def app[F[_] : Sym, A, B](f: F[A => B])
(a: F[A]) = Sym[F].app(f)(a)
def lam[F[_] : Sym, A, B](s: String)
(f: (F[A] => F[B])) = Sym[F].lam(s)(f)
}
type RunSym[A] = A
object RunSym extends Sym[RunSym] {
override def lit[A]: (String, A) => RunSym[A] =
(_, a) => a
override def app[A, B]: (RunSym[(A) => B]) =>
(RunSym[A]) => RunSym[B] = f => a => f(a)
override def lam[A, B]: String =>
((RunSym[A]) => RunSym[B]) => RunSym[(A) => B] =
s => f => f
}
type ShowSym[A] = String
object ShowSym extends Sym[ShowSym] {
override def lit[A]: (String, A) => ShowSym[A] =
(s, _) => s
override def app[A, B]: (ShowSym[(A) => B]) =>
(ShowSym[A]) => ShowSym[B] = f => a => s"$f($a)"
override def lam[A, B]: String =>
((ShowSym[A]) => ShowSym[B]) => ShowSym[(A) => B] =
s => f => s"($s => ${f(s)})"
}
import Sym._
def PLUS[F[_] : Sym] = lit("+",
(x: Int) => (y: Int) => x + y)
def main[F[_] : Sym] = lam("x")((x: F[Int]) =>
app(app(PLUS)(x))(x))
def answer[F[_] : Sym] = app(main)(lit(21))
println(s"${answer(ShowSym)} = ${answer(RunSym)}")
}
// (x => +(x)(x))(21) = 42