Skip to content

Instantly share code, notes, and snippets.

@VictorTaelin
Last active March 10, 2024 19:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save VictorTaelin/eeac9b51b664cec67c2009c9472d4ce5 to your computer and use it in GitHub Desktop.
Save VictorTaelin/eeac9b51b664cec67c2009c9472d4ce5 to your computer and use it in GitHub Desktop.
PHOAS in JS / HVM

ported from https://hub.darcs.net/dolio/agda-share/browse/PHOASNorm.agda

JS

const Lam = bod => lam => app => vah => lam(bod);
const App = fun => arg => lam => app => vah => app(fun)(arg);
const Var = exp => lam => app => vah => vah(exp);

const nz = v => nz => ns => nz(v);
const ns = t => nz => ns => ns(t);

const norm = expr => {
  let if_lam = bod => Lam(v => norm(bod(v)));
  let if_app = fun => arg => normapp(norm(fun))(arg);
  let if_var = exp => normvar(exp);
  return expr(if_lam)(if_app)(if_var); };

const normapp = fun => arg => {
  let if_lam = f_bod => norm(f_bod(ns(arg)));
  let if_app = f_fun => f_arg => App(fun)(norm(arg));
  let if_var = f_exp => {
    let if_nz = f_f => App(fun)(norm(arg));
    let if_ns = f_f => norm(App(f_f)(arg))
    return f_exp(if_nz)(if_ns); };
  return fun(if_lam)(if_app)(if_var); };

const normvar = exp => {
  let if_nz = f => Var(exp);
  let if_ns = f => norm(f);
  return exp(if_nz)(if_ns); };

const cut = expr => {
  let if_lam = bod => Lam(v => cut(bod(nz(v))));
  let if_app = fun => arg => App(cut(fun))(cut(arg));
  let if_var = exp => {
    let if_nz = f => Var(f);
    let if_ns = f => cut(f);
    return exp(if_nz)(if_ns); };
  return expr(if_lam)(if_app)(if_var); };

const show = (term, depth = 0) => {
  let if_lam = bod => {
    const body = show(bod(depth), depth + 1);
    return `λv${depth}. ${body}`;
  };
  let if_app = fun => arg => {
    const f = show(fun, depth);
    const x = show(arg, depth);
    return `(${f} ${x})`;
  };
  let if_var = exp => `v${exp}`;
  return term(if_lam)(if_app)(if_var);
};

var id = Lam(x => Var(x));
var c2 = Lam(f => Lam(x => App(Var(f))(App(Var(f))(Var(x)))));
var k2 = Lam(f => Lam(x => App(Var(f))(App(Var(f))(Var(x)))));

var term = App(c2)(c2);
var term = cut(norm(term));

console.log(show(term));

HVM

Lam = λnam λbod λlam λapp λvar (lam nam bod)
App = λfun λarg λlam λapp λvar (app fun arg)
Var = λnam      λlam λapp λvar (var nam)

View = λterm
  let clam = λnam λbod ((Lam) nam λx ((View) (bod ((Var) nam))))
  let capp = λfun λarg ((App) ((View) fun) ((View) arg))
  let cvar = λnam ((Var) nam)
  (term clam capp cvar)
  
Eval = λterm
  let clam = λnam λbod ((Lam) nam λx ((Eval) (bod x)))
  let capp = λfun λarg ((DoApp) ((Eval) fun) ((Eval) arg))
  let cvar = λnam ((Var) nam)
  (term clam capp cvar)

DoApp = λfun λarg
  let clam = λfnam λfbod λarg (fbod arg)
  let capp = λffun λfarg λarg ((App) ((App) ffun farg) arg)
  let cvar = λfnam       λarg ((App) ((Var) fnam) arg)
  (fun clam capp cvar arg)

Main = ((View) ((Eval) ((App)
  ((Lam) 0 λf ((Lam) 1 λx ((App) f ((App) f x))))
  ((Lam) 0 λx x))))
@VictorTaelin
Copy link
Author

From https://stackoverflow.com/questions/77332495/is-it-possible-using-phoas-to-evaluate-a-term-to-normal-form-and-then-stringi

A better version on HVM is:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}

import Data.Char

data Term v t where
   Lam :: (v a -> Term v b) -> Term v (a -> b)
   App :: Term v (a -> b) -> Term v a -> Term v b
   Var :: v t -> Term v t

data Exp t = Exp (forall v. Term v t)

data FreeTerm v a
  = Pure (v a)
  | Free (Term (FreeTerm v) a)

norm :: Term (FreeTerm v) a -> Term (FreeTerm v) a
norm (Lam bod) = Lam (\v -> norm (bod v))
norm (App fun arg) = case norm fun of
  Lam f_bod -> f_bod (Free arg)
  fun2      -> App fun2 (norm arg)
norm (Var (Pure x)) = Var (Pure x)
norm (Var (Free x)) = norm x

eval :: Exp a -> Exp a
eval (Exp x) = Exp (cut (norm x))

cut :: Term (FreeTerm v) a -> Term v a
cut (Lam bod)      = Lam (\x -> cut (bod (Pure x)))
cut (App fun arg)  = App (cut fun) (cut arg)
cut (Var (Pure x)) = Var x
cut (Var (Free x)) = cut x

data Name a = Name Int

showTermGo :: Int -> Term Name t -> String
showTermGo d (Lam a)        = "@x" ++ show d ++ " " ++ showTermGo (d+1) (a (Name d))
showTermGo d (App f x)      = "(" ++ showTermGo d f ++ " " ++ showTermGo d x ++ ")"
showTermGo _ (Var (Name i)) = "x" ++ show i

showTerm :: Exp t -> String
showTerm (Exp e) = showTermGo 0 e

main :: IO ()
main = do
  let c2 = (Lam (\f -> (Lam (\x -> (App (Var f) (App (Var f) (Var x)))))))
  let xp = (App c2 c2)
  putStrLn $ "term: " ++ showTerm (Exp xp)
  putStrLn $ "norm: " ++ showTerm (eval (Exp xp))

Translated to HVM, we have:

Lam = λbod      λlam λapp λvah (lam bod)
App = λfun λarg λlam λapp λvah (app fun arg)
Var = λexp      λlam λapp λvah (vah exp)

Pure = λv λpure λfree (pure v)
Free = λt λpure λfree (free t)

Norm = λexpr
  let if_lam = λbod ((Lam) λv ((Norm) (bod v)))
  let if_app = λfun λarg ((NormApp) ((Norm) fun) ((Norm) arg))
  let if_var = λexp ((NormVar) exp)
  (expr if_lam if_app if_var)

NormApp = λfun λarg
  let if_lam = λf_bod        λarg (f_bod ((Free) arg))
  let if_app = λf_fun λf_arg λarg ((App) ((App) f_fun f_arg) arg)
  let if_var = λf_hol        λarg ((App) ((Var) f_hol)       arg)
  (fun if_lam if_app if_var arg)

NormVar = λhol
  let if_pure = λx ((Var) ((Pure) x))
  let if_free = λx ((Norm) x)
  (hol if_pure if_free)
  
Cut = λexpr
  let if_lam = λbod ((Lam) λx ((Cut) (bod ((Pure) x))))
  let if_app = λfun λarg ((App) ((Cut) fun) ((Cut) arg))
  let if_var = λhol (hol λf((Var) f) λf((Cut) f))
  (expr if_lam if_app if_var)

Eval = λexpr ((Cut) ((Norm) expr))

C2 = ((Lam) λf ((Lam) λx ((App) ((Var) f) ((App) ((Var) f) ((Var) x)))))
C1 = ((Lam) λf ((Lam) λx ((App) ((Var) f) ((Var) x))))
ID = ((Lam) λx ((Var) x))

Main =
  let xp = ((App) C2 C2)
  let xp = ((Eval) xp)
  xp

@VictorTaelin
Copy link
Author

This version works on HVM2 with dups!! Sadly no 2^2 yet:

Lam = λbod      λlam λapp λvah (lam bod)
App = λfun λarg λlam λapp λvah (app fun arg)
Var = λexp      λlam λapp λvah (vah exp)

Pure = λv λpure λfree (pure v)
Free = λt λpure λfree (free t)

Quote = λterm λdepth
  let if_lam = λbod      λdepth ((Lam) depth ((Quote) (bod depth) (+ 1 depth)))
  let if_app = λfun λarg λdepth ((App) ((Quote) fun depth) ((Quote) arg depth))
  let if_var = λhol      λdepth ((Var) hol)
  (term if_lam if_app if_var depth)

Reduce = λexpr
  let if_lam = λbod ((Lam) λv (bod v))
  let if_app = λfun λarg ((ReduceApp) ((Reduce) fun) arg)
  let if_var = λexp ((ReduceVar) exp)
  (expr if_lam if_app if_var)

ReduceApp = λfun λarg
  let if_lam = λf_bod        λarg (f_bod ((Free) arg))
  let if_app = λf_fun λf_arg λarg ((App) ((App) f_fun f_arg) arg)
  let if_var = λf_hol        λarg ((App) ((Var) f_hol)       arg)
  (fun if_lam if_app if_var arg)

ReduceVar = λhol
  let if_pure = λx ((Var) ((Pure) x))
  let if_free = λx ((Reduce) x)
  (hol if_pure if_free)

Cut = λexpr
  let if_lam = λbod ((Lam) λx ((Cat) (bod ((Pure) x))))
  let if_app = λfun λarg ((App) ((Cat) fun) ((Cat) arg))
  let if_var = λhol (hol λf((Var) f) λf((Cat) f))
  (expr if_lam if_app if_var)

Cat = λexpr
  ((Cut) ((Reduce) expr))

Eval = λexpr
  ((Cat) expr)

C2 = ((Lam) λf ((Lam) λx ((App) ((Var) f) ((App) ((Var) f) ((Var) x)))))
K2 = ((Lam) λf ((Lam) λx ((App) ((Var) f) ((App) ((Var) f) ((Var) x)))))
C1 = ((Lam) λf ((Lam) λx ((App) ((Var) f) ((Var) x))))
ID = ((Lam) λx ((Var) x))

Main =
  let xp = ((App) C2 C1)
  let xp = ((Eval) xp)
  //let xp = ((Quote) xp 0)
  xp

@VictorTaelin
Copy link
Author

VictorTaelin commented Oct 22, 2023

So after thinking about what makes PHOAS work I realized we can just do this:

Lam =      λbod λlam λapp λvar (lam bod)
App = λarg λfun λlam λapp λvar (app arg fun)
Var = λexp      λlam λapp λvar (var exp)

View = λd λterm
  let clam = λbod      λd (Lam (View (+ 1 d) (bod (Var d))))
  let capp = λarg λfun λd (App (View d arg) (View d fun))
  let cvar = λexp      λd (Var exp)
  (term clam capp cvar d)
  
Eval = λterm
  let clam = λbod (Lam λx (Eval (bod (Var x))))
  let capp = λarg λfun (DoApp (Eval arg) (Eval fun))
  let cvar = λexp exp
  (term clam capp cvar)

DoApp = λfun λarg
  let clam = λfbod       λarg (fbod arg)
  let capp = λfarg λffun λarg (App (App farg ffun) arg)
  let cvar = λfexp       λarg (App (Var fexp) arg)
  (fun clam capp cvar arg)

ID    = (Lam λx x)
C0    = (Lam λf (Lam λx x))
C1    = (Lam λf (Lam λx (App f x)))
C2    = (Lam λf (Lam λx (App f (App f x))))
C3    = (Lam λf (Lam λx (App f (App f (App f x)))))
C4    = (Lam λf (Lam λx (App f (App f (App f (App f x))))))
False = (Lam λt (Lam λf f))
True  = (Lam λt (Lam λf t))
Not   = (Lam λb (App (App b False) True))

Main = (Eval (App (App C3 Not) True))

the only difference is that we add a Var constructor on the lam case of Eval, and remove that Var on the var case. I believe the issue was that Eval was reaching variables somehow caused disconnected graphs. for example, Eval (Lam (λx x)) would result in Lam (λx (Eval x)) - notice the Eval call stuck there. I believe that, since x was not substituted, and since Eval is recursive, it would expand infinitely. when we used View, this would be "fixed" because View substitutes x, but only for variables that are part of the normal form of the original program. so, if the input term itself had parts that got disconnected, then View would never be able to substitue some x's, causing Eval's somewhere to unroll infinitely. now, by using the Var constructor as a "guard" inside Eval itself, we will have that Eval (Lam (λx x)) will result in (Lam (λx (Eval (Var x))) , which returns (Lam (λx x)), i.e., Eval always disappears before having the change of expanding infinitely, and it doesn't need another function (like View) to "fix" it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment