As a learning exercise, I decided to try to convert the ML code on the Wikipedia page for normalisation by evaluation into Haskell. The main barrier (or so I thought) was that the ML code mutates a variable in order to get a supply of fresh names. I thought I was going to easily get around this by using de Bruijn indices instead.
The Wikipedia page is, possibly, abandoned, since most of the sample implementation dates from 2007. So I am not sure whether I have bungled this translation of the code to Haskell, or if the code snippets on Wikipedia are buggy/incomplete/abandoned. Maybe somebody can advise me on a better source, or point out obvious inadequacies in the code.
data Ty = Basic String | Arrow Ty Ty | Prod Ty Ty
deriving Show
data Tm = Var Int | Lam Tm | App Tm Tm | Pair Tm Tm | Fst Tm | Snd Tm
deriving Show
data Sem = LAM (Sem -> Sem) | PAIR Sem Sem | SYN Tm
instance Show Sem where
show = showSem
showSem (LAM a) = "LAM (Func)"
showSem (PAIR s1 s2) = "PAIR" ++ show s1 ++ show s2
showSem (SYN tm) = "SYN" ++ show tm
reflect :: Ty -> Tm -> Sem
reflect (Arrow a b) t = LAM (\ ss -> reflect b (App t (reify a ss)))
reflect (Prod a b) t = PAIR (reflect a (Fst t)) (reflect b (Snd t))
reflect (Basic _) t = SYN t
reify :: Ty -> Sem -> Tm
reify (Arrow a b) (LAM s) = Lam (reify b (s (reflect a (Var 0))))
reify (Prod a b) (PAIR s t) = Pair (reify a s) (reify b t)
reify (Basic _) (SYN t) = t
type Ctx = [Sem]
nbe_lookup :: Ctx -> Int -> Sem
nbe_lookup g n = g !! n
meaning :: Ctx -> Tm -> Sem
meaning g t = case t of
Var x -> nbe_lookup g x
Lam s -> LAM (\ ss -> meaning (ss:g) s)
App s t -> case meaning g s of LAM ss -> ss (meaning g t)
Pair s t -> PAIR (meaning g s) (meaning g t)
Fst s -> (case meaning g s of (PAIR ss tt) -> ss)
Snd t -> (case meaning g t of (PAIR ss tt) -> tt)
nbe :: Ty -> Tm -> Tm
nbe a t = reify a (meaning [] t)
k = Lam (Lam (Var 1))
s = Lam (Lam (Lam (App (App (Var 2) (Var 0)) (App (Var 1) (Var 0)))))
skk = App (App s k) k
-- nbe (Arrow (Basic "a", Basic "a")) skk