{-# LANGUAGE GADTs, UndecidableInstances, MultiParamTypeClasses, FlexibleInstances #-} -- Search for UndecidableInstances to see why this is needed {-| Implement monads by specifying primitive instructions and their operational semantics. This package is based on the \"The Operational Monad Tutorial\", published in Issue 15 of The Monad.Reader . You are reading the API reference. For more thorough documentation including design and implementation notes as well as a correctness proof, please consult the included documentation in @doc\/Documentation.md@, also available at . This API reference includes only basic example code. More intricate examples are available in the @doc\/examples@ directory, also available at . -} module Control.Monad.Operational ( -- * Basic usage Program, singleton, ProgramView, view, -- $example -- * Monad transformer ProgramT, ProgramViewT(..), viewT, liftProgram -- $exampleT ) where import Control.Monad.Identity import Control.Monad.Trans import Control.Applicative -- mtl classes to instantiate. -- Those commented out cannot be instantiated. For reasons see below. -- import Control.Monad.Cont.Class -- import Control.Monad.Error.Class -- import Control.Monad.Reader.Class import Control.Monad.State.Class -- import Control.Monad.Writer.Class {------------------------------------------------------------------------------ Program ------------------------------------------------------------------------------} {-| The abstract data type 'Program instr a' represents programs. * The type constructor @instr :: * -> *@ indexes the primitive instructions. * @a@ is the return type of a program. @'Program' instr@ is always a monad and automatically obeys the monad laws. -} type Program instr a = ProgramT instr Identity a -- | View type for inspecting the first instruction. -- It has two constructors 'Return' and @:>>=@. -- (For technical reasons, they are documented at 'ProgramViewT'.) type ProgramView instr a = ProgramViewT instr Identity a -- | View function for inspecting the first instruction. view :: Program instr a -> ProgramView instr a view = runIdentity . viewT {- $example /Example usage/ Stack machine from \"The Operational Monad Tutorial\". @ data StackInstruction a where Push :: Int -> StackInstruction () Pop :: StackInstruction Int @ @ type StackProgram a = Program StackInstruction a @ @ interpret :: StackProgram a -> (Stack Int -> a) interpret = eval . view where eval :: ProgramView StackInstruction a -> (Stack Int -> a) eval (Push a :>>= is) stack = interpret (is ()) (a:stack) eval (Pop :>>= is) (a:stack) = interpret (is a ) stack eval (Return a) stack = a @ Note that since 'ProgramView' is a GADT, the type annotation for @eval@ is mandatory. -} {------------------------------------------------------------------------------ ProgramT - monad transformer ------------------------------------------------------------------------------} {-| The abstract data type @'ProgramT' instr m a@ represents programs. * The type constructor @instr :: * -> *@ indexes the primitive instructions. * @m@ is the base monad, embedded with 'lift'. * @a@ is the return type of a program. @'ProgramT' instr m@ is a monad transformer and automatically obeys both the monad and the lifting laws. -} data ProgramT instr m a where Lift :: m a -> ProgramT instr m a Bind :: ProgramT instr m b -> (b -> ProgramT instr m a) -> ProgramT instr m a Instr :: instr a -> ProgramT instr m a -- basic instances instance Monad m => Monad (ProgramT instr m) where return = Lift . return (>>=) = Bind instance MonadTrans (ProgramT instr) where lift = Lift instance Monad m => Functor (ProgramT instr m) where fmap = liftM instance Monad m => Applicative (ProgramT instr m) where pure = return (<*>) = ap -- | Program made from a single primitive instruction. singleton :: instr a -> ProgramT instr m a singleton = Instr -- | View type for inspecting the first instruction. data ProgramViewT instr m a where Return :: a -> ProgramViewT instr m a (:>>=) :: instr b -> (b -> ProgramT instr m a ) -> ProgramViewT instr m a -- | View function for inspecting the first instruction. viewT :: Monad m => ProgramT instr m a -> m (ProgramViewT instr m a) viewT (Lift m) = m >>= return . Return viewT ((Lift m) `Bind` g) = m >>= viewT . g viewT ((m `Bind` g) `Bind` h) = viewT (m `Bind` (\x -> g x `Bind` h)) viewT ((Instr i) `Bind` g) = return (i :>>= g) viewT (Instr i) = return (i :>>= return) {-| Lift a plain sequence of instructions to a sequence of instructions over a monad 'm'. This is the counterpart of the 'lift' function from 'MonadTrans'. It can be defined as follows: @ liftProgram = eval . view where eval :: ProgramView instr a -> ProgramT instr m a eval (Return a) = return a eval (i :>>= k) = singleton i >>= liftProgram . k @ -} liftProgram :: Monad m => Program instr a -> ProgramT instr m a liftProgram (Lift m) = return (runIdentity m) liftProgram (m `Bind` k) = liftProgram m `Bind` (liftProgram . k) liftProgram (Instr i) = Instr i {- $exampleT /Example usage/ List monad transformer. @ data PlusI m a where Zero :: PlusI m a Plus :: ListT m a -> ListT m a -> PlusI m a @ @ type ListT m a = ProgramT (PlusI m) m a @ @ runList :: Monad m => ListT m a -> m [a] runList = eval <=< viewT where eval :: Monad m => ProgramViewT (PlusI m) m a -> m [a] eval (Return x) = return [x] eval (Zero :>>= k) = return [] eval (Plus m n :>>= k) = liftM2 (++) (runList (m >>= k)) (runList (n >>= k)) @ Note that since 'ProgramView' is a GADT, the type annotation for @eval@ is mandatory. -} {------------------------------------------------------------------------------ mtl instances * All of these instances need UndecidableInstances, because they do not satisfy the coverage condition. * We can only make instances of those classes that do not contain control operators. Control operators are functions like listen :: m a -> m (a,w) that take arguments of type m a and cannot be implemented with lift . See also Conor McBride's remark on haskell-cafe: http://www.haskell.org/pipermail/haskell-cafe/2010-April/076185.html ------------------------------------------------------------------------------} instance (MonadState s m) => MonadState s (ProgramT instr m) where get = lift get put = lift . put instance (MonadIO m) => MonadIO (ProgramT instr m) where liftIO = lift . liftIO {- Attempt to lift control operators anyway. WARNING: HERE BE DRAGONS -- lift a control operation with a single argument -- Unfortunately, I don't have a specifications in terms of laws yet -- I think it makes use of -- liftControlOp1 f (lift m >>= k) = lift m >>= liftControlOp1 f . k -- and is therefore completely useless. liftControlOp1 :: Monad m => (m a -> m b) -> (ProgramT instr m a -> ProgramT instr m b) liftControlOp1 f = join . lift . (eval <=< viewT) where eval :: ProgramViewT instr m a -> m (ProgramT instr m a) eval (Return a) = f (return a) eval (i :>>= k) = i :>>= liftControlOp1 f . k instance (MonadWriter w m) => MonadWriter w (ProgramT instr m) where tell = lift . tell listen = liftControlOp1 listen pass = liftControlOp1 pass -}