rebound-0.1.0.0: examples/DepMatch.hs
-- | A dependent type system, with nested dependent pattern matching for Sigma types.
-- This is an advanced usage of the binding library, demonstrating the use of Scoped patterns.
-- It doesn't correspond to any current system, but has its own elegance
{-# LANGUAGE OverloadedLists #-}
module DepMatch where
import Rebound
import Rebound.Context
import qualified Rebound.Bind.Pat as Pat
import qualified Rebound.Bind.Scoped as Scoped
import Rebound.Bind.PatN as PN
import Control.Monad (guard, zipWithM_)
import Control.Monad.Except (ExceptT, MonadError (..), runExceptT)
import Data.Fin
import Data.Maybe qualified as Maybe
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Vec qualified
import Data.Scoped.List (List, pattern Nil, pattern (:<))
import Data.Scoped.List qualified as List
import GHC.Generics (Generic1)
-- In this system, `Match` introduces a Pi type and generalizes
-- dependent functions
-- If the pattern is a single variable, or an annotated variable,
-- then the `Match` term is just a normal lambda expression.
-- But the pattern could be more structured than that, supporting
-- a general form of pattern matching. In this simple language,
-- only type that supports pattern matching is a Sigma type. So
-- every match expression should have a single branch. But, for
-- generality, we pretend that more are possible.
data Exp (n :: Nat)
= Star
| Pi (Exp n) (Bind1 Exp Exp n)
| Var (Fin n)
| Match (List Branch n) -- case lambda
| App (Exp n) (Exp n)
| Sigma (Exp n) (Bind1 Exp Exp n)
| Pair (Exp n) (Exp n)
| Annot (Exp n) (Exp n)
deriving (Generic1)
-- | A single branch in a match expression
data Branch (n :: Nat)
= forall p. Branch (Scoped.Bind Exp Exp (Pat p) n)
-- | Patterns, which may include embedded type annotations
-- `p` is the number of variables bound by the pattern
-- `n` is the number of free variables in type annotations in the pattern
data Pat (p :: Nat) (n :: Nat) where
PVar :: Pat N1 n
-- Patterns are "telescopic"
-- In Pair pattern, we increase the scope so that variables
-- bound in the left subterm can be referred to in the right subterm
PPair :: Pat p1 n -> Pat p2 (p1 + n) -> Pat (p2 + p1) n
-- Patterns can also include type annotations.
PAnnot :: Pat p n -> Exp n -> Pat p n
-- This definitions support telescopes: variables bound earlier in the pattern
-- can appear later. For example, the pattern for a type paired with
-- a term of that type can look like this
-- (x, (y :: x))
pat0 :: Pat N2 N0
pat0 = PPair PVar (PAnnot PVar (Var f0))
-- The type of this pattern is
-- Sigma x:Star.x
ty0 :: Exp Z
ty0 = Sigma Star (bind1 (Var f0))
-------------------------------------------------------
-- definitions for pattern matching
-------------------------------------------------------
instance Sized (Pat p n) where
type Size (Pat p n) = p
size :: Pat p n -> SNat p
size PVar = s1
size (PPair p1 p2) = sPlus (size p2) (size p1)
size (PAnnot p _) = size p
-- Because Pat is a scope-indexed pattern, we need to also
-- instantiate the `ScopedSized` class
instance Scoped.ScopedSized (Pat p) where
type ScopedSize (Pat p) = p
-- A term that matches the "(x,(y:x))" and has type exists x:*. x
tm0 :: Exp Z
tm0 = Pair Star ty0
-- >>> patternMatch pat0 tm0
-- Just [(0,Sigma *. 0),(1,*)]
-- | Compare a pattern with an expression, potentially
-- producing a substitution for all of the variables
-- bound in the pattern
patternMatch :: Pat p n -> Exp n -> Maybe (Env Exp p n)
patternMatch PVar e = Just $ oneE e
patternMatch (PPair p1 p2) (Pair e1 e2) =
-- two append operations require implicit sizes in the context
withSNat (size p1) $ withSNat (size p2) $ do
env1 <- patternMatch p1 e1
-- NOTE: substitute in p2 with env1 before pattern matching
env2 <- patternMatch (applyE (env1 .++ idE) p2) e2
return (env2 .++ env1)
-- ignore type annotates when pattern matching
patternMatch (PAnnot p _) e = patternMatch p e
patternMatch p (Annot e _) = patternMatch p e
patternMatch _ _ = Nothing
findBranch :: Exp n -> List Branch n -> Maybe (Exp n)
findBranch e Nil = Nothing
findBranch e (Branch (bnd :: Scoped.Bind Exp Exp (Pat p) n) :< brs) =
case patternMatch (Scoped.getPat bnd) e of
Just r -> Just $ Scoped.instantiate bnd r
Nothing -> findBranch e brs
----------------------------------------------
-- * Subst instances
instance SubstVar Exp where
var = Var
instance Shiftable Exp where
shift = shiftFromApplyE @Exp
instance Subst Exp Exp where
isVar (Var x) = Just (Refl, x)
isVar _ = Nothing
{-
-- The generic definition above is equivalent to this code
applyE r Star = Star
applyE r (Pi a b) = Pi (applyE r a) (applyE r b)
applyE r (Var x) = applyEnv r x
applyE r (App e1 e2) = App (applyE r e1) (applyE r e2)
applyE r (Sigma a b) = Sigma (applyE r a) (applyE r b)
applyE r (Pair a b) = Pair (applyE r a) (applyE r b)
applyE r (Match brs) = Match (List.map (applyE r) brs)
applyE r (Annot a t) = Annot (applyE r a) (applyE r t)
-}
instance Shiftable (Pat p) where
shift = shiftFromApplyE @Exp
-- This definition cannot be generic because Pat is a GADT
instance Subst Exp (Pat p) where
applyE :: Env Exp n m -> Pat p n -> Pat p m
applyE r PVar = PVar
-- need to account for new pattern variables from p1 bound in p2
applyE r (PPair p1 p2) = PPair (applyE r p1) (applyE (upN (size p1) r) p2)
applyE r (PAnnot p t) = PAnnot (applyE r p) (applyE r t)
instance Shiftable Branch where
shift = shiftFromApplyE @Exp
-- This definition also cannot be generic due to the existential
instance Subst Exp Branch where
applyE :: Env Exp n m -> Branch n -> Branch m
applyE r (Branch b) = Branch (applyE r b)
----------------------------------------------
-- Free variable calculation
----------------------------------------------
t00 :: Exp N2
t00 = App (Var f0) (Var f0)
t01 :: Exp N2
t01 = App (Var f0) (Var f1)
-- >>> appearsFree f0 t00
-- True
-- >>> appearsFree f1 t00
-- False
instance FV Exp where
{-
-- Generic programming produces the following definitions:
appearsFree n (Var x) = n == x
appearsFree n Star = False
appearsFree n (Pi a b) = appearsFree n a || appearsFree (FS n) (getBody1 b)
appearsFree n (App a b) = appearsFree n a || appearsFree n b
appearsFree n (Sigma a b) = appearsFree n a || appearsFree (FS n) (getBody1 b)
appearsFree n (Pair a b) = appearsFree n a || appearsFree n b
appearsFree n (Match b) = List.any (appearsFree n) b
appearsFree n (Annot a t) = appearsFree n a || appearsFree n t
freeVars :: Exp n -> Set (Fin n)
freeVars (Var x) = Set.singleton x
freeVars Star = Set.empty
freeVars (Pi a b) = freeVars a <> rescope s1 (freeVars (getBody1 b))
freeVars (App a b) = freeVars a <> freeVars b
freeVars (Sigma a b) = freeVars a <> rescope s1 (freeVars (getBody1 b))
freeVars (Pair a b) = freeVars a <> freeVars b
freeVars (Match b) = List.foldMap freeVars b
freeVars (Annot a t) = freeVars a <> freeVars t
-}
-- cannot be generic
instance FV Branch where
appearsFree n (Branch bnd) = appearsFree n bnd
freeVars (Branch bnd)= freeVars bnd
-- cannot be generic
instance FV (Pat p) where
appearsFree n PVar = False
appearsFree n (PPair p1 p2) = appearsFree n p1 || appearsFree (shiftN (size p1) n) p2
appearsFree n (PAnnot p t) = appearsFree n p || appearsFree n t
freeVars PVar = Set.empty
freeVars (PPair p1 p2) = freeVars p1 <> rescope (size p1) (freeVars p2)
freeVars (PAnnot p t) = freeVars p <> freeVars t
----------------------------------------------
-- weakening (convenience functions)
----------------------------------------------
-- >>> :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)
weakenBind' :: SNat m -> Bind1 Exp Exp n -> Bind1 Exp Exp (m + n)
weakenBind' m = applyE @Exp (weakenE' m)
----------------------------------------------
-- strengthening
----------------------------------------------
-- >>> strengthenRec s1 s1 snat t00
-- Just (0 0)
-- >>> strengthenRec s1 s1 snat t01
-- Nothing
instance Strengthen Exp where
{-
strengthenRec k m n (Var x) = Var <$> strengthenRec k m n x
strengthenRec k m n Star = pure Star
strengthenRec k m n (Pi a b) = Pi <$> strengthenRec k m n a <*> strengthenRec k m n b
strengthenRec k m n (App a b) = App <$> strengthenRec k m n a <*> strengthenRec k m n b
strengthenRec k m n (Pair a b) = Pair <$> strengthenRec k m n a <*> strengthenRec k m n b
strengthenRec k m n (Sigma a b) = Sigma <$> strengthenRec k m n a <*> strengthenRec k m n b
strengthenRec k m n (Match b) = Match <$> List.mapM (strengthenRec k m n) b
strengthenRec k m n (Annot a t) = Annot <$> strengthenRec k m n a <*> strengthenRec k m n t
-}
instance Strengthen (Pat p) where
strengthenRec k m n PVar = pure PVar
strengthenRec (k :: SNat k) (m :: SNat m) (n :: SNat n) (PPair (p1 :: Pat p1 (k + (m + n)))
(p2 :: Pat p2 (p1 + (k + (m + n))))) =
case (axiomAssoc @p1 @k @(m + n),
axiomAssoc @p1 @k @n) of
(Refl, Refl) ->
let r = strengthenRec (sPlus (size p1) k) m n p2 in
PPair <$> strengthenRec k m n p1 <*> r
strengthenRec k m n (PAnnot p1 e2) = PAnnot <$> strengthenRec k m n p1 <*> strengthenRec k m n e2
instance Strengthen Branch where
strengthenRec k m n (Branch bnd) = Branch <$> strengthenRec k m n bnd
----------------------------------------------
-- Some Examples
----------------------------------------------
star :: Exp n
star = Star
-- No annotation on the binder
lam :: Exp (S n) -> Exp n
lam b = Match [Branch (Scoped.bind PVar b)]
-- Annotation on the binder
alam :: Exp n -> Exp (S n) -> Exp n
alam t b = Match [Branch (Scoped.bind (PAnnot PVar t) b)]
-- The identity function "λ x. x". With de Bruijn indices
-- we write it as "λ. 0", though with `Match` it looks a bit different
t0 :: Exp Z
t0 = lam (Var f0)
-- A larger term "λ x. λy. x (λ z. z z)"
-- λ. λ. 1 (λ. 0 0)
t1 :: Exp Z
t1 =
lam
( lam
(Var f1 `App` lam (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 `unbind`
-- 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 (lam (Var f0))
-- >>> tyid
-- Pi *. 0 -> 1
-- >>> tmid
-- λ_. (λ_. 0)
--------------------------------------------------------
-- * Show instances
--------------------------------------------------------
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 (Pair e1 e2) =
showParen (d > 0) $
showsPrec 10 e1
. showString ", "
. showsPrec 11 e2
showsPrec d (Match [b]) =
showParen (d > 9) $
showString "λ"
. showsPrec 9 b
showsPrec d (Match b) =
showParen (d > 10) $
showString "match"
. showsPrec 10 b
showsPrec d (Annot a t) =
showParen (d > 10) $
showsPrec 10 a
. showString " : "
. showsPrec 10 t
instance Show (Branch b) where
showsPrec d (Branch b) =
showsPrec 10 (Scoped.getPat b)
. showString ". "
. showsPrec 11 (Scoped.getBody b)
instance Show (Pat p n) where
showsPrec d PVar = showString "_"
showsPrec d (PPair e1 e2) =
showParen (d > 0) $
showsPrec 10 e1
. showString ", "
. showsPrec 11 e2
showsPrec d (PAnnot e1 e2) =
showParen (d > 0) $
showsPrec 10 e1
. showString " : "
. showsPrec 11 e2
--------------------------------------------------------
-- * Alpha equivalence
--------------------------------------------------------
-- The derivable equality instance is alpha-equivalence
deriving instance (Eq (Exp n))
instance PatEq (Pat p1 n) (Pat p2 n) where
patEq :: Pat p1 n -> Pat p2 n -> Maybe (p1 :~: p2)
patEq PVar PVar = Just Refl
patEq (PPair p1 p2) (PPair p1' p2') = do
Refl <- patEq p1 p1'
Refl <- patEq p2 p2'
return Refl
patEq (PAnnot p1 p2) (PAnnot p1' p2') = do
Refl <- patEq p1 p1'
guard (p2 == p2')
return Refl
patEq _ _ = Nothing
-- This equality is not derivable
instance Eq (Branch n) where
(==) :: Branch n -> Branch n -> Bool
(Branch (p1 :: Scoped.Bind Exp Exp (Pat m1) n))
== (Branch (p2 :: Scoped.Bind Exp Exp (Pat m2) n)) =
case testEquality
(size (Scoped.getPat p1) :: SNat m1)
(size (Scoped.getPat p2) :: SNat m2) of
Just Refl -> p1 == p2
Nothing -> False
--------------------------------------------------------
-- * big-step evaluation
--------------------------------------------------------
-- We can write the usual operations for evaluating
-- lambda terms to values
-- >>> eval t1
-- λ_. (λ_. (1 (λ_. (0 0))))
-- >>> eval (t1 `App` t0)
-- λ_. ((λ_. 0) (λ_. (0 0)))
eval :: Exp n -> Exp n
eval (Var x) = Var x
eval (Match b) = Match b
eval (App e1 e2) =
let v = eval e2
in case eval e1 of
Match b -> case findBranch v b of
Just e -> eval e
Nothing -> error "pattern match failure"
t -> App t v
eval Star = Star
eval (Pi a b) = Pi a b
eval (Sigma a b) = Sigma a b
eval (Annot a t) = eval a
eval (Pair a b) = Pair a b
-- small-step evaluation
-- >>> step (t1 `App` t0)
-- Just (λ_. (λ_. 0 (λ_. (0 0))))
step :: Exp n -> Maybe (Exp n)
step (Var x) = Nothing
step (Match b) = Nothing
step (App (Match bs) e2)
| Just r <- findBranch e2 bs =
Just r
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 (Annot a t) = step a
eval' :: Exp n -> Exp n
eval' e
| Just e' <- step e = eval' e'
| otherwise = e
----------------------------------------------------------------
-- Check for equality
----------------------------------------------------------------
data Err where
NotEqual :: Exp n -> Exp n -> Err
PiExpected :: Exp n -> Err
PiExpectedPat :: Pat p1 n1 -> Err
SigmaExpected :: Exp n -> Err
VarEscapes :: Exp n -> Err
PatternMismatch :: Pat p1 n1 -> Pat p2 n2 -> Err
PatternTypeMismatch :: Pat p1 n1 -> Exp n1 -> Err
AnnotationNeeded :: Exp n -> Err
AnnotationNeededPat :: Pat p1 n1 -> Err
deriving instance (Show Err)
-- find the head form
whnf :: Exp n -> Exp n
whnf (App a1 a2) = case whnf a1 of
Match bs -> case findBranch (eval a2) bs of
Just b -> whnf b
Nothing -> App (Match bs) a2
t -> App t a2
whnf (Annot a t) = whnf a
whnf a = a
equate :: (MonadError Err m) => Exp n -> Exp n -> m ()
equate t1 t2 = do
let n1 = whnf t1
n2 = whnf t2
equateWHNF n1 n2
equatePat ::
(MonadError Err m) =>
Pat p1 n ->
Pat p2 n ->
m ()
equatePat PVar PVar = pure ()
equatePat (PPair p1 p1') (PPair p2 p2')
| Just Refl <- testEquality (size p1) (size p2) =
equatePat p1 p2 >> equatePat p1' p2'
equatePat (PAnnot p1 e1) (PAnnot p2 e2) =
equatePat p1 p2 >> equate e1 e2
equatePat p1 p2 = throwError (PatternMismatch p1 p2)
equateBranch :: (MonadError Err m) => Branch n -> Branch n -> m ()
equateBranch (Branch b1) (Branch b2) =
let p1 = Scoped.getPat b1
p2 = Scoped.getPat b2
body1 = Scoped.getBody b1
body2 = Scoped.getBody b2
in
case testEquality (size p1) (size p2) of
Just Refl ->
equatePat p1 p2 >> equate body1 body2
Nothing ->
throwError (PatternMismatch (Scoped.getPat b1) (Scoped.getPat b2))
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 ()
(Match b1, Match b2) ->
List.zipWithM_ equateBranch b1 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)
(Sigma tyA1 b1, Sigma tyA2 b2) -> do
equate tyA1 tyA2
equate (getBody1 b1) (getBody1 b2)
(_, _) -> throwError (NotEqual n1 n2)
----------------------------------------------------------------
-- * Type checking
----------------------------------------------------------------
inferPattern ::
(MonadError Err m) =>
Ctx Exp n -> -- input context
Pat p n -> -- pattern to check
m (Ctx Exp (p + n), Exp (p + n), Exp n)
inferPattern g (PAnnot p ty) = do
(g', e) <- checkPattern g p ty
pure (g', e, ty)
inferPattern g p = throwError (AnnotationNeededPat p)
-- | type check a pattern and produce an extended typing context,
-- plus expression form of the pattern (for dependent pattern matching)
checkPattern ::
(MonadError Err m) =>
Ctx Exp n -> -- input context
Pat p n -> -- pattern to check
Exp n -> -- expected type of pattern (should be in whnf)
m (Ctx Exp (p + n), Exp (p + n))
checkPattern g PVar a = do
pure (g +++ a, var f0)
checkPattern g (PPair (p1 :: Pat p1 n) (p2 :: Pat p2 (p1 + n))) (Sigma tyA tyB) = do
-- need to know that Plus is associative
case axiomAssoc @p2 @p1 @n of
Refl -> do
(g', e1) <- checkPattern g p1 tyA
let tyB' = weakenBind' (size p1) tyB
let tyB'' = whnf (instantiate1 tyB' e1)
(g'', e2) <- checkPattern g' p2 tyB''
let e1' = weaken' (size p2) e1
return (g'', Pair e1' e2)
checkPattern g p ty = do
(g', e, ty') <- inferPattern g p
equate ty ty'
return (g', e)
-----------------------------------------------------------
-- Checking branches
-----------------------------------------------------------
-- G |- p : A => G' G' |- b : B { p / x}
-- ----------------------------------------------
-- G |- p => b : Pi x : A . B
checkBranch ::
(MonadError Err m) =>
Ctx Exp n ->
Exp n ->
Branch n ->
m ()
checkBranch g (Pi tyA tyB) (Branch bnd) = do
let pat = Scoped.getPat bnd
let body = Scoped.getBody bnd
let p = size pat
-- find the extended context and pattern expression
(g', a) <- checkPattern g pat tyA
-- shift tyB to the scope of the pattern and instantiate it with 'a'
-- must be done simultaneously because 'a' is from a larger scope
let tyB' = applyE (a .: shiftNE p) (getBody1 tyB)
-- check the body of the branch in the scope of the pattern
checkType g' body tyB'
checkBranch g t e = throwError (PiExpected t)
-- should only check with a type in whnf
checkType ::
(MonadError Err m) =>
Ctx Exp n ->
Exp n ->
Exp n ->
m ()
checkType 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)
_ -> throwError (SigmaExpected ty)
checkType g (Match bs) ty = do
List.mapM_ (checkBranch g ty) bs
checkType g e t1 = do
t2 <- inferType g e
equate (whnf t2) t1
-- | infer the type of an expression. This type may not
-- necessarily be in whnf
inferType ::
(MonadError Err m) =>
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 (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 a =
throwError (AnnotationNeeded a)
-- >>> tmid
-- λ_. (λ_. 0)
-- >>> tyid
-- Pi *. 0 -> 1
-- >>> :t tyid
-- tyid :: Exp n
-- >>> (checkType zeroE tmid tyid :: Either Err ())
-- Right ()
-- >>> (inferType zeroE (App tmid tyid) :: Either Err (Exp N0))
-- Left (AnnotationNeeded (λ_. (λ_. 0)))