{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE UnicodeSyntax #-} -- | This is a demonstration of using open recursion to implement the -- judgements of a small theory modularly. module Control.Monad.Trans.Open.Example ( -- * Syntax Ty(..) , Tm(..) -- * Judgement Forms , J(..) -- * Theories , Theory , unitThy , prodThy , combinedThy -- * Result , judge -- ** Injecting Effects , traceThy , Pack(..) , tracedJudge ) where import Control.Applicative import Control.Monad.Open.Class import Control.Monad.Trans.Open import Control.Monad.Trans import Control.Monad.Identity import Control.Monad.Trans.Maybe import Control.Monad.Writer import Data.Monoid -- | The syntax of terms in our theory. data Ty = Unit | Prod Ty Ty deriving (Eq, Show) -- | The syntax of terms in our theory. -- data Tm = Ax | Pair Tm Tm deriving (Eq, Show) -- | Next, the forms of judgements are inductively defined. We index the -- 'J' type by its synthesis. -- -- ['DisplayTy'] To know @'DisplayTy' α@ is to know the textual notation for the type @α@. -- ['DisplayTm'] To know @'DisplayTm' m@ is to know the textual notation for the term @m@. -- ['Check'] To know @'Check' α m@ is to know that @m@ is a canonical verification of @α@. -- data J a where DisplayTy ∷ Ty → J String DisplayTm ∷ Tm → J String Check ∷ Ty → Tm → J Bool deriving instance Show (J a) -- | A @'Theory' j@ is an open, partial implementation of the judgements -- defined by the judgement signature @j@. Theories may be composed, since -- @'Monoid' ('Theory' j)@ holds. -- type Theory j = ( Monad m , Alternative m , MonadOpen (j a) a m ) ⇒ Op (j a) m a -- | A 'Theory' implementing the judgements as they pertain to the 'Unit' type former. -- unitThy ∷ Theory J unitThy = Op $ \case DisplayTy Unit → return "unit" DisplayTm Ax → return "ax" Check Unit m → return $ m == Ax _ → empty -- | A 'Theory' implementing the judgments as they pertain to the 'Prod' type former. -- prodThy ∷ Theory J prodThy = Op $ \case DisplayTy (Prod a b) → do x ← call $ DisplayTy a y ← call $ DisplayTy b return $ "(" ++ x ++ " * " ++ y ++ ")" DisplayTm (Pair m n) → do x ← call $ DisplayTm m y ← call $ DisplayTm n return $ "<" ++ x ++ ", " ++ y ++ ">" Check (Prod a b) mn → case mn of Pair m n → (&&) <$> call (Check a m) <*> call (Check b n) _ → return False _ → empty -- | The horizontal composition of the two above theories. -- -- @ -- 'combinedThy' = 'unitThy' '<>' 'prodThy' -- @ -- combinedThy ∷ Theory J combinedThy = unitThy <> prodThy -- | Judgements may be tested through the result of closing the theory. -- -- @ -- 'judge' = 'close' 'combinedThy' -- @ -- -- >>> judge $ DisplayTy $ Prod Unit (Prod Unit Unit) -- "(unit * (unit * unit))" -- -- >>> judge $ DisplayTm $ Pair Ax (Pair Ax Ax) -- ">" -- -- >>> judge $ Check (Prod Unit Unit) Ax -- False -- -- >>> judge $ Check (Prod Unit (Prod Unit Unit)) (Pair Ax (Pair Ax Ax)) -- True -- judge = close combinedThy data Pack φ = forall a. Pack (φ a) instance Show (Pack J) where show (Pack j) = show j -- | We can inject a log of all assertions into the theory! -- traceThy ∷ ( Monad m , Alternative m , MonadOpen (j a) a m , MonadWriter [Pack J] m ) ⇒ Op (J a) m a traceThy = Op $ \j → do tell $ [Pack j] empty -- | A traced judging routine is constructed by precomposing 'traceThy' onto the main theory. -- -- @ -- 'tracedJudge' j = 'runIdentity' . 'runWriterT' . 'runMaybeT' $ ('close' $ 'traceThy' '<>' 'combinedThy') j -- @ -- -- >>> tracedJudge $ Check (Prod Unit Unit) (Pair Ax Ax) -- (Just True,[Check (Prod Unit Unit) (Pair Ax Ax),Check Unit Ax,Check Unit Ax]) -- -- >>> tracedJudge $ Check (Prod Unit Unit) (Pair Ax (Pair Ax Ax)) -- (Just False,[Check (Prod Unit Unit) (Pair Ax (Pair Ax Ax)),Check Unit Ax,Check Unit (Pair Ax Ax)]) -- tracedJudge ∷ J b → (Maybe b, [Pack J]) tracedJudge j = runIdentity . runWriterT . runMaybeT $ (close $ traceThy <> combinedThy) j