rebound-0.1.0.0: examples/PTS.hs
-- Pure-type system-like example
-- Includes Pi/Sigma, untyped equivalence
module PTS where
import Rebound
import qualified Rebound.Bind.Pat as Pat
import Rebound.Bind.PatN as PatN
import Rebound.Context
import Control.Monad.Except (ExceptT, MonadError (..), runExceptT)
import Data.Fin(Fin(..), f0,f1,f2)
import Data.Fin qualified as Fin
import Data.Vec qualified as Vec
import GHC.Generics (Generic1)
-- In a pure type system, terms and types are combined
-- into the same syntactic class.
data Exp (n :: Nat) where
-- | sort
Star :: Exp n
-- | dependent type `Pi x : A . B`
Pi :: Exp n -> Bind1 Exp Exp n -> Exp n
-- | variable
Var :: Fin n -> Exp n
-- | lambda expression, with type annotation `lambda x:A.B`
Lam :: Exp n -> Bind1 Exp Exp n -> Exp n
-- | application
App :: Exp n -> Exp n -> Exp n
-- | dependent pair `Sigma x:A . B`
Sigma :: Exp n -> Bind1 Exp Exp n -> Exp n
-- | construct a pair, third argument is type annotation
Pair :: Exp n -> Exp n -> Exp n -> Exp n
-- | elimination form for pairs. `split e1 as (x,y) in e2`
-- Binds two variables to
-- the two components of the pair
Split :: Exp n -> Bind2 Exp Exp n -> Exp n
deriving instance (Generic1 Exp)
----------------------------------------------
instance SubstVar Exp where
var :: Fin n -> Exp n
var = Var
instance Shiftable Exp where
shift = shiftFromApplyE @Exp
instance Subst Exp Exp where
isVar (Var x) = Just (Refl, x)
isVar _ = Nothing
----------------------------------------------
t00 :: Exp N2
t00 = App (Var f0) (Var f0)
t01 :: Exp N2
t01 = App (Var f0) (Var f1)
-- Does a variable appear free in a term?
-- >>> appearsFree f0 t00
-- True
-- >>> appearsFree f1 t00
-- False
instance FV Exp where
-- >>> :t weaken' s1 t00
-- weaken' s1 t00 :: Exp ('S ('S N1))
-- >>> weaken' s1 t00
-- 0 0
weaken' :: SNat m -> Exp n -> Exp (m + n)
weaken' m = applyE @Exp (weakenE' m)
-- >>> strengthenRec s1 s1 snat t00
-- Just (0 0)
-- >>> strengthenRec s1 s1 snat t01
-- Nothing
instance Strengthen Exp where
----------------------------------------------
-- Examples
-- The identity function "λ x. x". With de Bruijn indices
-- we write it as "λ. 0"
t0 :: Exp Z
t0 = Lam Star (bind1 (Var f0))
-- A larger term "λ x. λy. x (λ z. z z)"
-- λ. λ. 1 (λ. 0 0)
t1 :: Exp Z
t1 =
Lam
Star
( bind1
( Lam
Star
( bind1
( Var f1
`App` (Lam Star (bind1 (Var f0)) `App` Var f0)
)
)
)
)
-- To show lambda terms, we can write a simple recursive instance of
-- Haskell's `Show` type class. In the case of a binder, we use the `unbind1`
-- operation to access the body of the lambda expression.
-- >>> t0
-- λ *. 0
-- >>> t1
-- λ *. λ *. 1 ((λ *. 0) 0)
-- Polymorphic identity function and its type
tyid = Pi Star (bind1 (Pi (Var f0) (bind1 (Var f1))))
tmid = Lam Star (bind1 (Lam (Var f0) (bind1 (Var f0))))
-- >>> tyid
-- Pi *. 0 -> 1
-- >>> tmid
-- λ *. λ 0. 0
instance Show (Exp n) where
showsPrec :: Int -> Exp n -> String -> String
showsPrec _ Star = showString "*"
showsPrec d (Pi a b)
| appearsFree FZ (getBody1 b) =
showParen (d > 9) $
showString "Pi "
. shows a
. showString ". "
. shows (getBody1 b)
| otherwise =
showParen (d > 9) $
showsPrec 11 a
. showString " -> "
. showsPrec 9 (getBody1 b)
showsPrec d (Sigma a b)
| appearsFree FZ (getBody1 b) =
showParen (d > 9) $
showString "Sigma "
. shows a
. showString ". "
. shows (getBody1 b)
| otherwise =
showParen (d > 9) $
showsPrec 11 a
. showString " * "
. showsPrec 9 (getBody1 b)
showsPrec _ (Var x) = shows x
showsPrec d (App e1 e2) =
showParen (d > 0) $
showsPrec 10 e1
. showString " "
. showsPrec 11 e2
showsPrec d (Lam t b) =
showParen (d > 9) $
showString "λ "
. shows t
. showString ". "
. shows (getBody1 b)
showsPrec d (Pair e1 e2 t) =
showParen (d > 0) $
showsPrec 10 e1
. showString ", "
. showsPrec 11 e2
showsPrec d (Split t b) =
showParen (d > 10) $
showString "split"
. showsPrec 10 t
. showString " in "
. shows (getBody2 b)
-- With the instance above the derivable equality instance
-- is alpha-equivalence
deriving instance (Eq (Exp n))
--------------------------------------------------------
{- We can write the usual operations for evaluating
lambda terms to values -}
-- big-step evaluation
-- >>> eval t1
-- λ *. λ *. 1 ((λ *. 0) 0)
-- >>> eval (t1 `App` t0)
-- λ *. (λ *. 0) ((λ *. 0) 0)
eval :: Exp n -> Exp n
eval (Var x) = Var x
eval (Lam a b) = Lam a b
eval (App e1 e2) =
let v = eval e2
in case eval e1 of
Lam a b -> eval (instantiate1 b v)
t -> App t v
eval Star = Star
eval (Pi a b) = Pi a b
eval (Sigma a b) = Sigma a b
eval (Pair a b t) = Pair a b t
eval (Split a b) =
case eval a of
Pair a1 a2 _ ->
eval (instantiate2 b (eval a1) (eval a2))
t -> Split t b
-- small-step evaluation
-- >>> step (t1 `App` t0)
-- Just (λ *. λ *. 0 (λ *. 0 0))
step :: Exp n -> Maybe (Exp n)
step (Var x) = Nothing
step (Lam a b) = Nothing
step (App (Lam a b) e2) = Just (instantiate1 b e2)
step (App e1 e2)
| Just e1' <- step e1 = Just (App e1' e2)
| Just e2' <- step e2 = Just (App e1 e2')
| otherwise = Nothing
step Star = Nothing
step (Pi a b) = Nothing
step (Sigma a b) = Nothing
step (Pair a b _) = Nothing
step (Split (Pair a1 a2 _) b) = Just (PatN.instantiate2 b a1 a2)
step (Split a b)
| Just a' <- step a = Just (Split a' b)
| otherwise = Nothing
eval' :: Exp n -> Exp n
eval' e
| Just e' <- step e = eval' e'
| otherwise = e
-- normalization
-- to normalize under a lambda expression, we must first unbind
-- it and then rebind it when finished
-- >>> nf t1
-- λ. λ. 1 0
-- >>> nf (t1 `App` t0)
-- λ *. 0
-- reduce the term everywhere, as much as possible
nf :: Exp n -> Exp n
nf (Var x) = Var x
nf (Lam a b) = Lam a (bind1 (nf (getBody1 b)))
nf (App e1 e2) =
case nf e1 of
Lam a b -> nf (instantiate1 b e2)
t -> App t (nf e2)
nf Star = Star
nf (Pi a b) = Pi (nf a) (bind1 (nf (getBody1 b)))
nf (Sigma a b) = Sigma (nf a) (bind1 (nf (getBody1 b)))
nf (Pair a b t) = Pair (nf a) (nf b) (nf t)
nf (Split a b) =
case nf a of
Pair a1 a2 _ -> nf (PatN.instantiate2 b a1 a2)
t -> Split t (PatN.bind2 (nf (getBody2 b)))
-- first find the head form
whnf :: Exp n -> Exp n
whnf (App a1 a2) = case whnf a1 of
Lam a b -> whnf (instantiate1 b a1)
t -> App t a2
whnf (Split a b) = case whnf a of
Pair a1 a2 _ -> whnf (PatN.instantiate2 b a1 a2)
t -> Split t b
-- all other terms are already in head form
whnf a = a
norm :: Exp n -> Exp n
norm a = case whnf a of
Lam a b -> Lam (norm a) (bind1 (norm (getBody1 b)))
Pi a b -> Pi (norm a) (bind1 (norm (getBody1 b)))
Sigma a b -> Sigma (norm a) (bind1 (norm (getBody1 b)))
Pair a b t -> Pair (norm a) (norm b) (norm t)
Star -> Star
App a b -> App a (norm b)
Split a b -> Split a (PatN.bind2 (norm (getBody2 b)))
Var x -> Var x
--------------------------------------------------------
-- We can also write functions that manipulate the
-- environment explicitly
-- >>> evalEnv idE t1
-- λ *. λ *. 1 ((λ *. 0) 0)
-- Below, if n is 0, then this function acts like an
-- "environment-based" bigstep evaluator. The result of
-- evaluating a lambda expression is a closure --- the body
-- of the lambda paired with its environment. That is exactly
-- what the implementation of bind1 does.
-- In the case of beta-reduction, the `unBindWith` operation
-- applies its argument to the environment and subterm in the
-- closure. In other words, this function calls `evalEnv`
-- recursively with the saved environment and body of the lambda term.
evalEnv :: Env Exp m n -> Exp m -> Exp n
evalEnv r (Var x) = applyEnv r x
evalEnv r (Lam a b) = applyE r (Lam a b)
evalEnv r (App e1 e2) =
let v = evalEnv r e2
in case evalEnv r e1 of
Lam a b ->
unbindWith1 b (\r' e' -> evalEnv (v .: r') e')
t -> App t v
evalEnv r Star = Star
evalEnv r (Pi a b) = applyE r (Pi a b)
evalEnv r (Sigma a b) = applyE r (Sigma a b)
evalEnv r (Pair a b t) = applyE r (Pair a b t)
evalEnv r (Split a b) =
case evalEnv r a of
Pair a1 a2 _ ->
PatN.unbindWith2 b ( \r' e' -> evalEnv (a1 .: (a2 .: (r' .>> r))) e')
t -> Split t (applyE r b)
----------------------------------------------------------------
data Err where
Equate :: Exp n -> Exp n -> Err
PiExpected :: Exp n -> Err
SigmaExpected :: Exp n -> Err
VarEscapes :: Exp n -> Err
deriving instance (Show Err)
equate :: (MonadError Err m) => Exp n -> Exp n -> m ()
equate t1 t2 = do
let n1 = whnf t1
n2 = whnf t2
equateWHNF n1 n2
equateWHNF :: (MonadError Err m) => Exp n -> Exp n -> m ()
equateWHNF n1 n2 =
case (n1, n2) of
(Star, Star) -> pure ()
(Var x, Var y) | x == y -> pure ()
(Lam _ b1, Lam _ b2) -> equate (getBody1 b1) (getBody1 b2)
(App a1 a2, App b1 b2) -> do
equateWHNF a1 b1
equate a2 b2
(Pi tyA1 b1, Pi tyA2 b2) -> do
equate tyA1 tyA2
equate (getBody1 b1) (getBody1 b2)
(Pair a1 a2 _, Pair b1 b2 _) -> do
equate a1 b1
equate a2 b2
(Split a1 b1, Split a2 b2) -> do
equateWHNF a1 a2
equate (getBody2 b1) (getBody2 b2)
(Sigma tyA1 b1, Sigma tyA2 b2) -> do
equate tyA1 tyA2
equate (getBody1 b1) (getBody1 b2)
(_, _) -> throwError (Equate n1 n2)
----------------------------------------------------------------
checkType ::
forall n m.
(MonadError Err m, SNatI n) =>
Ctx Exp n ->
Exp n ->
Exp n ->
m ()
checkType g e t1 = do
t2 <- inferType g e
equate (whnf t2) t1
inferType ::
forall n m.
(MonadError Err m, SNatI n) =>
Ctx Exp n ->
Exp n ->
m (Exp n)
inferType g (Var x) = pure (applyEnv g x)
inferType g Star = pure Star
inferType g (Pi a b) = do
checkType g a Star
checkType (g +++ a) (getBody1 b) Star
pure Star
inferType g (Lam tyA b) = do
checkType g tyA Star
tyB <- inferType (g +++ tyA) (getBody1 b)
return $ Pi tyA (bind1 tyB)
inferType g (App a b) = do
tyA <- inferType g a
case whnf tyA of
Pi tyA1 tyB1 -> do
checkType g b tyA1
pure $ instantiate1 tyB1 b
t -> throwError (PiExpected t)
inferType g (Sigma a b) = do
checkType g a Star
checkType (g +++ a) (getBody1 b) Star
pure Star
inferType g (Pair a b ty) = do
tyA <- inferType g a
tyB <- inferType g b
case ty of
(Sigma tyA tyB) -> do
checkType g a tyA
checkType g b (instantiate1 tyB a)
pure ty
_ -> throwError (SigmaExpected ty)
inferType g (Split a b) = do
tyA <- inferType g a
case whnf tyA of
Sigma tyA' tyB' -> do
let g' :: Ctx Exp (S (S n))
g' = g +++ tyA' +++ getBody1 tyB'
ty <- inferType g' (getBody2 b)
let ty' = whnf ty
case strengthenN s2 ty' of
Nothing -> throwError (VarEscapes ty)
Just ty'' -> pure ty''
_ -> throwError (SigmaExpected tyA)
-- >>> inferType zeroE tmid :: Either Err (Exp N0)
-- Right (Pi *. 0 -> 1)
-- >>> inferType zeroE (App tmid tyid) :: Either Err (Exp N0)
-- Right ((Pi *. 0 -> 1) -> Pi *. 0 -> 1)