{-| Module : System F Description : Syntax and reductions of System F using the Nominal package Copyright : (c) Murdoch J. Gabbay, 2020 License : GPL-3 Maintainer : murdoch.gabbay@gmail.com Stability : experimental Portability : POSIX Syntax and reductions of System F using the Nominal package -} {-# LANGUAGE DataKinds , InstanceSigs , DeriveAnyClass , DeriveGeneric , MultiParamTypeClasses , FlexibleInstances , LambdaCase -- , PolyKinds , DefaultSignatures , DeriveAnyClass , DeriveDataTypeable , DeriveGeneric , EmptyCase , FlexibleInstances , FlexibleContexts -- so we can write `Swappable d` , InstanceSigs , LambdaCase , MultiParamTypeClasses , StandaloneDeriving , TypeOperators , UndecidableInstances #-} module Language.Nominal.Examples.SystemF ( -- * Introduction -- $intro ATrm, ATyp, -- * System F types NTypLabel, NTyp, Typ (..), typRecurse, -- * System F terms NTrmLabel, NTrm, Trm (..), typeOf, typeOf', typeable, -- * Normal forms nf, nf', normalisable, -- * Pretty-printer PP (..), -- * Helper functions for building terms tall, tlam, lam, (@.), (@:), -- * Example terms idTrm, idTrm2, zero, suc, one, nat, church, transform, selfapp -- * Tests -- $tests ) where import Data.Generics hiding (Generic, typeOf) import Data.Maybe import GHC.Generics import Control.Monad (guard) import Language.Nominal.Utilities import Language.Nominal.Name import Language.Nominal.Binder import Language.Nominal.Abs import Language.Nominal.Sub {- $intro System F is a classic example and has some interesting features: * Two kinds of variable: /type variables/ and /term variables/. * Three kinds of binding: /type forall/ binding a type variable in a type; /term lambda/ binding a term variable in a term; and /type lambda/ binding a type variable in a term. * A static assignment of semantic information to term variables, namely: a /type assignment/. Thus intuitively term variables carry labels (types), which themselves may contain type variables. * And it's an expressive system of independent mathematical interest. So implementing System F is a natural test for this package. We start with atoms: -} -- | With @DataKinds@, we obtain: -- -- * @ATyp@ a type of atoms to identify type variables @'NTyp'@, and -- * @ATrm@ a type of atoms to identify term variables @'NTrm'@. -- -- See 'Language.Nominal.Name.Tom' for more discussion of how this works. data ATyp deriving (Data) -- | With @DataKinds@, we obtain: -- -- * @ATyp@ a type of atoms to identify type variables @'NTyp'@, and -- * @ATrm@ a type of atoms to identify term variables @'NTrm'@. -- -- See 'Language.Nominal.Name.Tom' for more discussion of how this works. data ATrm deriving (Data) -- * System F types -- | A type variable is labelled just by a display name type NTypLabel = String -- | A type variable name. Internally, this consists of -- -- * an atom of type @KAtom ATyp@, and -- * a label of type @'NTypLabel'@, which is just a display name in @'String'@. type NTyp = KName ATyp NTypLabel -- | Datatype of System F types -- -- We use @Generic@ to deduce a swapping action for atoms of sorts @'ATyp'@ and @'ATrm'@. -- Just once, we spell out the definition implicit in the generic instance: -- -- > instance Swappable Typ where -- > swpN n1 n2 (TVar n) = TVar $ swpN n1 n2 n -- > swpN n1 n2 (t' :-> t) = swpN n1 n2 t' :-> swpN n1 n2 t -- > swpN n1 n2 (All x) = All $ swpN n1 n2 x -- -- This is boring, and automated, and that's the point: swappings distribute uniformly through everything including abstractors (the @x@ under the @All@). -- -- (The mathematical reason for this is that swappings are invertible, whereas renamings and substitutions aren't.) -- data Typ = TVar NTyp -- ^ Type variable | Typ :-> Typ -- ^ Type application | All (KAbs NTyp Typ) -- ^ Type forall-abstraction deriving (Eq, Generic, Swappable, Typeable, Data) -- | Substitution acts on type variables. Capture-avoidance is automagical. instance KSub NTyp Typ Typ where sub :: NTyp -> Typ -> Typ -> Typ sub a t = rewrite $ \case -- 'rewrite' comes from Scrap-Your-Boilerplate generics. TVar n | n == a -> Just t -- note name-equality is atom-wise and ignores labels _ -> Nothing -- | Nominal recursion scheme. We never use it because it's implicit in pattern-matching. See e.g. code for 'typeOf', 'nf', and 'ppp'. typRecurse :: (NTyp -> a) -> (Typ -> Typ -> a) -> (NTyp -> Typ -> a) -> Typ -> a typRecurse f1 _ _ (TVar n) = f1 n typRecurse _ f2 _ (s1 :-> s2) = f2 s1 s2 typRecurse _ _ f3 (All x') = x' @@! f3 ------------------------------------ -- * System F terms -- | A term variable is labelled by a display name, and its type type NTrmLabel = ( String -- Display name of term variable , Typ -- Type of the term variable ) -- | A term variable name type NTrm = KName ATrm NTrmLabel -- | Substitute type variables with type in term variable. -- Non-trivial because a term variable carries a label which contains a type. -- Action is functorial, descending into the type label. instance KSub NTyp Typ NTrm where sub a ty' (Name lab atm) = Name (sub a ty' lab) atm -- | System F terms. -- -- * We get swapping actions automatically, and also substitution of type names @NTyp@ for types @Typ@. -- * Substitution of term variables @NTrm@ for terms @Trm@ needs defined separately. -- data Trm = Var NTrm -- ^ Term variable, labelled by its display name and type | App Trm Trm -- ^ Apply a term to a term | Lam (KAbs NTrm Trm) -- ^ Nominal atoms-abstraction by a term variable. | TApp Trm Typ -- ^ Apply a term to a type | TLam (KAbs NTyp Trm) -- ^ Nominal atoms-abstraction by a type variable. deriving ( Eq , Generic , Swappable --- swappings derived automatically , KSub NTyp Typ --- substitution of type names for types derived automatically , Typeable , Data ) -- | Substitute term variable with term in term instance KSub NTrm Trm Trm where sub :: NTrm -> Trm -> Trm -> Trm sub a t = rewrite $ \case -- 'rewrite' comes from Scrap-Your-Boilerplate generics. Var n | n == a -> Just t -- note name-equality is atom-wise and ignores labels _ -> Nothing {-- Var n -> toMaybe (a == n) t -- note name-equality is atom-wise and ignores labels _ -> Nothing --} -- | Calculate type of term, maybe typeOf :: Trm -> Maybe Typ typeOf (Var n) = let (_, t) = nameLabel n in Just t typeOf (TLam (tp :@> tm)) = do -- Maybe monad typetm <- typeOf tm return $ All (tp :@> typetm) typeOf (Lam (n :@> tm)) = do -- Maybe monad typetm <- typeOf tm let (_, t) = nameLabel n return $ t :-> typetm typeOf (App s1 s2) = do -- Maybe monad t1a :-> t1b <- typeOf s1 t2 <- typeOf s2 guard (t1a == t2) return t1b typeOf (TApp s t) = do -- Maybe monad All x' <- typeOf s return $ x' `conc` t -- substitution of type name for type, in type -- | Calculate type of term; raise error if none exists typeOf' :: Trm -> Typ typeOf' s = fromMaybe (error ("Type error" ++ ppp s)) (typeOf s) -- | @'True'@ if term is typeable; @'False'@ if not. typeable :: Trm -> Bool typeable = isJust . typeOf -- * Normal forms -- | Normal form, maybe nf :: Trm -> Maybe Trm nf s = guard (typeable s) >> return (repeatedly nf_ s) where -- behaviour on untypeable terms is undefined nf_ :: Trm -> Trm nf_ = rewrite $ \case -- 'rewrite' comes from Scrap-Your-Boilerplate generics. TApp (TLam x') t2 -> return . nf_ $ x' `conc` t2 App (Lam x') s2 -> return . nf_ $ x' `conc` (nf_ s2) _ -> Nothing -- | Normal form; raise error if none nf' :: Trm -> Trm nf' s = fromMaybe (error ("Type error: " ++ ppp s)) (nf s) -- | True if term is normalisable; false if not. normalisable :: Trm -> Bool normalisable = isJust . nf {--------------------------------------------- -- Semantics data Sem = SemLam Typ (Sem -> Sem) | SemTLam (Typ -> Sem) -- We get sub automagically type TypeVarContext = Name NTypLabel -> Typ type TermVarContext = Name NTrmLabel -> Sem sem :: TypeVarContext -> TermVarContext -> Trm -> Maybe Sem sem tyc tmc (Var n) = Just $ tmc n -- ^ Look up in the var context sem tyc tmc (App s1 s2) = do -- Maybe monad SemLam t f <- sem tyc tmc s1 f <$> (sem tyc tmc s2) sem tyc tmc (Lam x) = x @@! \n s -> do -- Maybe monad (_, nty) <- nameLabel n return $ SemLam nty (\x -> sem tyc (sub n x tmc) s) sem tyc tmc (TApp s1 t2) = do -- Maybe monad SemTLam f <- sem tyc tmc s1 return $ f t2 sem tyc tmc (TLam x) = x @@! \n s -> SemTLam (\x -> sem (sub n x tyc) tmc s) -- A useful property would be that sem (t t') = sem t (sem t') --} --------------------------------------------- -- * Pretty-printer -- | Typeclass for things that can be pretty-printed class PP a where ppp :: a -> String pp :: a -> IO () pp = putStrLn . ppp -- | Pretty-print type variable instance PP NTyp where ppp n = (nameLabel n) ++ "(" ++ show (nameAtom n) ++ ")" -- | Pretty-print type instance PP Typ where ppp (TVar n) = ppp n ppp (All (n :@> t)) = '∀':(ppp n ++ "." ++ ppp t) ppp (t :-> u) = pppL t ++ " -> " ++ pppR u where pppL (All _) = "(" ++ ppp t ++ ")" pppL (_ :-> _) = "(" ++ ppp t ++ ")" pppL _ = ppp t pppR (All _) = "(" ++ ppp u ++ ")" pppR _ = ppp u -- | Pretty-print term variable instance PP NTrm where ppp n = (\(s, t) -> s ++ ":" ++ ppp t) (nameLabel n) ++ "(" ++ show (nameAtom n) ++ ")" -- Forall ∀ -- Capital Lambda Λ = \0923 -- lambda λ = \0955 -- | Pretty-print term instance PP Trm where ppp (Lam (n :@> t)) = "λ" ++ pppN n ++ pppB t where pppB (Lam (n' :@> t')) = "," ++ " " ++ pppN n' ++ pppB t' pppB expr = '.':ppp expr pppN n' = let (s', t') = nameLabel n' in (s' ++ ":" ++ ppp t') ppp (TLam (n :@> t)) = "Λ" ++ ppp n ++ pppB t where pppB (TLam (n' :@> t')) = " " ++ ppp n' ++ pppB t' pppB expr = '.':ppp expr ppp (Var s) = ppp s ppp (App x y) = pppL x ++ pppR y where pppL (Lam _) = "(" ++ ppp x ++ ")" pppL _ = ppp x pppR (Var s) = ' ':ppp s pppR _ = "(" ++ ppp y ++ ")" ppp (TApp x y) = pppL x ++ "[" ++ ppp y ++ "]" where pppL (Lam _) = "(" ++ ppp x ++ ")" pppL _ = ppp x -- ppp (Let x y z) = -- "let " ++ x ++ " = " ++ ppp y ++ " in " ++ ppp z instance {-# OVERLAPS #-} Show Trm where show = ppp instance {-# OVERLAPS #-} Show (Maybe Trm) where show = maybe "No term!" ppp instance {-# OVERLAPS #-} Show Typ where show = ppp instance {-# OVERLAPS #-} Show (Maybe Typ) where show = maybe "No type!" ppp -- * Helper functions for building terms -- | Build type quantification from function: f ↦ ∀ a.(f a) for fresh a tall :: NTypLabel -> (Typ -> Typ) -> Typ tall s f = All $ absFresh s (f . TVar) -- | Build type lambda from function: f ↦ Λ a.(f a) for fresh a tlam :: NTypLabel -> (Typ -> Trm) -> Trm tlam s f = TLam $ absFresh s (f . TVar) -- | Build term lambda from function: f ↦ λ a.(f a) for fresh a lam :: NTrmLabel -> (Trm -> Trm) -> Trm lam (s,ty) f = Lam $ absFresh (s, ty) (f . Var) -- | Term-to-term application (@.) :: Trm -> Trm -> Trm s1 @. s2 = App s1 s2 -- | Term-to-type application (@:) :: Trm -> Typ -> Trm s1 @: t2 = TApp s1 t2 -- * Example terms -- | polymorphic identity term = λX x:X.x idTrm :: Trm idTrm = TLam $ absFresh "X" (\xx -> Lam $ absFresh ("x", TVar xx) (\x -> Var x)) -- | Another rendering of polymorphic identity term idTrm2 :: Trm idTrm2 = tlam "X" $ \xx -> lam ("x", xx) $ \x -> x -- hlint complains about \x -> x. retained for clarity -- | 0 = λX λs:X->X λz:X.z zero :: Trm zero = tlam "X" $ \xx -> lam ("s", xx :-> xx) $ \_s -> lam ("z", xx) $ \z -> z -- hlint complains about \x -> x. retained for clarity -- | suc = λx:(∀X.(X->X)->X->X) X s:X->X z:X.s(n[X] s z) suc :: Trm suc = lam ("n",nat) $ \n -> tlam "X" $ \xx -> lam ("s", xx :-> xx) $ \s -> lam ("z", xx) $ \z -> s @. (((n @: xx) @. s) @. z) -- | 1 one :: Trm one = nf' (suc @. zero) -- | nat nat :: Typ nat = tall "X" $ \xx -> (xx :-> xx) :-> (xx :-> xx) -- | Cast an Int to the corresponding Church numeral. church :: Int -> Trm church 0 = zero church i = suc @. church (i-1) -- | Transformer type = ∀X.X->X transform :: Typ transform = tall "X" $ \xx -> xx :-> xx -- | Self-application = λx:∀X.X->X.x[∀X.X->X] x selfapp :: Trm selfapp = lam ("x", transform) $ \x -> (x @: transform) @. x {- $tests Property-based tests are in "Language.Nominal.Properties.Examples.SystemFSpec". -}