{-# LANGUAGE DeriveDataTypeable, DeriveGeneric, FlexibleContexts #-}
module TinyLam where
import GHC.Generics (Generic)
import Data.Typeable (Typeable)
import Control.Applicative (Applicative(..))
import Control.Monad.Reader
import Data.Monoid (Monoid(..))
import qualified Data.List
import Test.Tasty
import Test.Tasty.HUnit
import Unbound.Generics.LocallyNameless
type Var = Name Expr
data ArithOp = ArithOp String (Int -> Int -> Int)
deriving (Typeable, Generic)
instance Show ArithOp where
show (ArithOp f _) = f
data Expr = V Var
| I Int
| Arith ArithOp Expr Expr
| Lam Fun
| App Expr Expr
| If0 Expr Expr Expr
| Letrec (Bind (Rec (Var, Embed Fun)) Expr)
deriving (Typeable, Generic, Show)
newtype Fun = Fun (Bind Var Expr)
deriving (Typeable, Generic, Show)
instance Eq Expr where
(==) = aeq
instance Eq Fun where
(==) = aeq
-- leaf instance for ArithOp
instance Alpha ArithOp where
aeq' _ctx (ArithOp s1 _) (ArithOp s2 _) = s1 == s2
fvAny' _ctx _nfn x = pure x
close _ctx _b x = x
open _ctx _b x = x
isPat _ = mempty
isTerm _ = mempty
nthPatFind _ = mempty
namePatFind _ = mempty
swaps' _ctx _p x = x
freshen' _ctx x = return (x, mempty)
lfreshen' _ctx x cont = cont x mempty
acompare' _ctx (ArithOp s1 _) (ArithOp s2 _) = compare s1 s2
instance Alpha Expr
instance Alpha Fun
type Env = [(Var, Value)]
data Value = VI !Int
| VClo !Env !Fun
deriving (Eq, Show)
emptyEnv :: Env
emptyEnv = []
extendEnv :: Var -> Value -> Env -> Env
extendEnv x v rho = (x,v) : rho
env :: MonadReader Env m => m Env
env = ask
lookupV :: MonadReader Env m => Var -> m Value
lookupV x = env >>= \rho ->
case Data.List.lookup x rho of
Just v -> return v
Nothing -> error $ "unbound variable " ++ show x
eval :: (Fresh m, MonadReader Env m) => Expr -> m Value
eval (V x) = lookupV x
eval (I i) = return $ VI i
eval (Lam f) = env >>= \rho -> return (VClo rho f)
eval (App e1 e2) = do
v1 <- eval e1
case v1 of
(VClo rho (Fun bnd)) -> do
v2 <- eval e2
(x, e3) <- unbind bnd
local (const $ extendEnv x v2 rho) $ eval e3
_ -> error ("expected a function, but got " ++ show v1)
eval (Arith (ArithOp _ op) e1 e2) = do
v1 <- eval e1
v2 <- eval e2
case (v1, v2) of
(VI n1, VI n2) -> return $ VI (op n1 n2)
_ -> error ("expected pair of ints, but got " ++ show v1
++ " and " ++ show v2)
eval (If0 e1 e2 e3) = do
v1 <- eval e1
case v1 of
VI 0 -> eval e2
VI _ -> eval e3
_ -> error ("expected int, but got " ++ show v1)
eval (Letrec bnd) = do
(r, ebody) <- unbind bnd
rho0 <- ask
let (f, Embed fun) = unrec r
-- N.B. knot tying
rho = extendEnv f vclo rho0
vclo = VClo rho fun
local (extendEnv f vclo) $ eval ebody
runEval :: Expr -> Value
runEval e = runReader (runFreshMT (eval e)) emptyEnv
ex_f :: Int -> Expr
ex_f n = let
(/*/) = Arith (ArithOp "*" (*))
(/-/) = Arith (ArithOp "-" (-))
fact = s2n "fact"
x = s2n "x"
body = If0 (V x)
{-then-} (I 1)
{-else-} (V x
/*/
App (V fact) (V x /-/ I 1))
factfun = Fun (bind x body)
in Letrec $ bind (rec (fact, Embed factfun))
(App (V fact) (I n))
test_ex_f0 :: TestTree
test_ex_f0 =
testCase "eval (fact 0) = 1"
$ assertEqual "" (runEval (ex_f 0)) (VI 1)
test_ex_f5 :: TestTree
test_ex_f5 =
testCase "eval (fact 5) = 120"
$ assertEqual "" (runEval (ex_f 5)) (VI 120)
test_tinyLam :: TestTree
test_tinyLam =
testGroup "tinyLam"
[ test_ex_f0
, test_ex_f5
]