Collapsing towers

Normalization by evaluation in ML

This section is mostly copied from Wikiedia: Normalization by evaluation

datatype ty = Basic of string
            | Arrow of ty * ty
            | Prod of ty * ty
s,t,... ::= var x | lam (x, t) | app (s, t) | pair (s, t) | fst | snd t
datatype tm = var of string
            | lam of string * tm | app of tm * tm
            | pair of tm * tm | fst of tm | snd of tm
datatype sem = LAM of (sem -> sem)
             | PAIR of sem * sem
             | SYN of tm
(* reflect : ty -> tm -> sem *)
fun reflect (Arrow (a, b)) t =
    LAM (fn S => reflect b (app t (reify a S)))
  | reflect (Prod (a, b)) t =
    PAIR (reflect a (fst t)) (reflect b (snd t))
  | reflect (Basic _) t =
    SYN t
(* reify : ty -> sem -> tm *)
fun reify (Arrow (a, b)) (LAM S) =
      let x = fresh_var () in
        Lam (x, reify b (S (reflect a (var x))))
  | reify (Prod (a, b)) (PAIR S T) =
      Pair (reify a S, reify b T)
  | reify (Basic _) (SYN t) = t
(* meaning : ctx -> tm -> sem *)
fun meaning G t =
      case t of
        var x =>
          lookup G x
      | lam (x, s) =>
          LAM (fn S => meaning (add G (x, S)) s)
      | app (s, t) => (case meaning G s of LAM S => S (meaning G t))
      | pair (s, t) => PAIR (meaning G s, meaning G t) 
      | fst s => (case meaning G s of PAIR (S, T) => S)
      | snd t => (case meaning G t of PAIR (S, T) => T)

Lambda-up-down

Collapsing Towers of Interpreters (POPL 2018)

The Base.lift function implements the reify operation, which in this case converts a semantic value to some syntax that will construct the same value. Numeric constant values Cst(n) are turned into numeric literal expressions Lit(n). String values Strn(s) are converted to symbols Sym(s) (evalms evaluats symbols to strings, variables are not symbols). Since variables are not values they are not mentioned in lift.

The conversion of Clo (closures) is more complex. The collapsing towers extract below also implements memoization of the resulting lambda term for any closure. I beleive it searches stFun for elements that have equal environment and body expression using collectFirst, If it finds a match it extracts n from the element and executes the case Some(n) clause. This clause only emits a variable referencing the existing lambda term. If no matching entry in stFun is found it will execute the case None clause. This will in turn a store the a new element in stFun containing the fresh n together with the closure environment and expression in stFun. The stFun variable is only used by Base.lift for relating closures to memoized lambda terms.

def lift(v: Val): Exp = v match {
  case Cst(n) =>
    Lit(N)
  case Tup(a,b) =>
    val (Code(u),Code(v)) = (a,b)
    reflect(Cons(u,v))
  case Clo(env2,e2) =>
    stFun collectFirst { case (n `env2`, `e2`) => n } match {
      case Some(n) =>
        Var(n)
      case None =>
        stFun :+= (stFresh, env2, e2)
        reflect(Lam(reify{
            val Code(r) = evalms(env2:+Code(fresh()):+Code(fresh()),e2);
            r}))
    }
  case Code(e) =>
    reflect(Lift(e))
def run[A](f: => A): A = {
  val sF = stFresh
  val sB = stBlock
  val sN = stFun
  try f finally {
    stFresh = sF;
    stBlock = sB;
    stFun = sN
  }
}
def reify(f: => Exp) = run {
  stBlock = Nil
  val last = f
  (stBlock foldRight last)(Let)
}
def reifyc(f: => Val) = reify { val Code(e) = f; e }
def reflectc(s: Exp) = Code(reflect(s))
def reifyv(f: => Val) = run {
  stBlock = Nil
  val res = f
  if (stBlock != Nil) {
    val Code(last) = res
    Code((stBlock foldRight last)(Let))
  } else {
    res
  }
}
def reflect(s:Exp) = {
  stBlock :+= s
  fresh()
}
type Env = List[Val]
abstract class Val
case class Cst(n:Int)          extends Val
case class Str(s:String)       extends Val
case class Clo(env:Env,e:Exp)  extends Val
case class Tup(v1:val,v2:Val)  extends Val
case class Code(e:Exp)        extends Val
abstract class Exp
case class Lit(n:Int) extends Exp
case class Sym(s:String) extends Exp
case class Var(n:Int) extends Exp
case class App(e1:Exp, e2:Exp) extends Exp
case class Lam(e:Exp) extends Exp
case class Let(e1:Exp,e2:Exp) extends Exp
case class If(c:Exp,a:Exp,b:Exp) extends Exp
case class Plus(a:Exp,b:Exp) extends Exp
case class Minus(a:Exp,b:Exp) extends Exp
case class Times(a:Exp,b:Exp) extends Exp
case class Equ(a:Exp,b:Exp) extends Exp
case class Conss(a:Exp,b:Exp) extends Exp
case class Fst(a:Exp) extends Exp
case class Snd(a:Exp) extends Exp
case class IsNum(a:Exp) extends Exp
case class IsStr(a:Exp) extends Exp
case class IsCons(a:Exp) extends Exp
case class Lift(e:Exp) extends Exp
case class Run(b:Exp,e:Exp) extends Exp

Design

Values

Val ::= [#code ???]
     |  [#cons Val Val]
     |  [#nil]
     |  [#sym Name]
     |  [#clo Val Val Val Env]
     |  [#wrap Val]
     |  [#envp Sym Val Env]
     |  [#envn]
     |  [#op \vau]
     |  [#op \wrap]
     |  [#op \unwrap]
     |  [#op \lift]
     |  [#op \run]
     |  [#op \eval]
     |  [#op \cons]
     |  [#op \first]
     |  [#op \rest]
     |  [#op \cons?]

Expressions

Most values do not have forms of literal expressions, only symbols and lists do.

Exp ::= Symbol
     |  List

Evaluation

[\eval   [#code EXPR LETS] ENV] = ???
[\eval   [#cons COMB ARGS] ENV] = [combine [\eval COMB ENV] ARGS ENV]
[\eval   [#nil] _] = error
[\eval S@[#symbol ...] ENV]    = [lookup S ENV]
[\eval C@[#clo ...] _] = C
[\eval W@[#wrap ...] _] = W
[\eval E@[#envp ...] _] = E
[\eval E@[#envn] _] = E
[\eval O@[#op ...] _] = O
[lookup  S@[#symbol ...] [#envp S V _]] = V
[lookup S1@[#symbol ...] [#envp S2 _ E]] = [lookup S1 E] iff S1 /= S2 
[lookup  S@[#symbol ...] [#envn]] = error
[combine [#code ???] ARGS ENV] = ???
[combine [#cons Val Val] ARGS ENV] = error
[combine [#nil] ARGS ENV] = error
[combine [#sym Name] ARGS ENV] = error
[combine [#clo Val Val Val Env] ARGS ENV] = todo
[combine [#wrap Val] ARGS ENV] = todo
[combine [#envp Sym Val Env] ARGS ENV] = error
[combine [#envn] ARGS ENV] = error
[combine [#op \vau] ARGS ENV] = todo
[combine [#op \wrap] ARGS ENV] = todo
[combine [#op \unwrap] ARGS ENV] = todo
[combine [#op \lift] ARGS ENV] = todo
[combine [#op \run] ARGS ENV] = todo
[combine [#op \eval] ARGS ENV] = todo
[combine [#op \cons] ARGS ENV] = todo
[combine [#op \first] ARGS ENV] = todo
[combine [#op \rest] ARGS ENV] = todo
[combine [#op \cons?] ARGS ENV] = todo