operational 0.2.3.5 → 0.2.4.0
raw patch · 4 files changed
+79/−20 lines, 4 filesdep ~base
Dependency ranges changed: base
Files
- CHANGELOG.md +6/−0
- doc/examples/TicTacToe.hs +1/−1
- operational.cabal +1/−1
- src/Control/Monad/Operational.hs +71/−18
CHANGELOG.md view
@@ -1,6 +1,12 @@ Changelog for the `operational` package --------------------------------------- +**0.2.4.0** -- Feature release.++* Update to build with GHC 9.0.1.+* Add utility functions `interpretWithMonadT`, `unviewT` and `mapInstr`+* Add utility `Functor`, `Applicative`, and `Monad` instances for `ProgramViewT` type.+ **0.2.3.5** -- Maintenance release. * Update references to other packages.
doc/examples/TicTacToe.hs view
@@ -20,7 +20,7 @@ import Control.Monad.State import Data.Either-import Data.List+import Data.List (transpose, intersperse) -- external libraries needed import System.Random
operational.cabal view
@@ -1,5 +1,5 @@ Name: operational-Version: 0.2.3.5+Version: 0.2.4.0 Synopsis: Implementation of difficult monads made easy with operational semantics. Description:
src/Control/Monad/Operational.hs view
@@ -5,25 +5,25 @@ module Control.Monad.Operational ( -- * Synopsis -- $synopsis- + -- * Overview -- $intro- + -- * Monad Program, singleton, ProgramView, view, -- $example interpretWithMonad,- + -- * Monad transformer ProgramT, ProgramViewT(..), viewT, -- $exampleT- liftProgram,- + liftProgram, mapInstr,+ unviewT, interpretWithMonadT,+ ) 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.@@ -95,7 +95,7 @@ you can wait for the user to return input as shown, or you store the continuation @k@ and retrieve it when your web application receives another HTTP request,-or you can keep a log of all user inputs on the client side an replay them,+or you can keep a log of all user inputs on the client side and replay them, and so on. Moreover, you can implement different @run@ functions for one and the same custom monad, which is useful for testing. Also note that the result type of the @run@ function does not need to@@ -120,9 +120,9 @@ i.e. sequences of primitive instructions. * The /primitive instructions/ are given by the type constructor @instr :: * -> *@.- + * @a@ is the return type of a program.- + @'Program' instr@ is always a monad and automatically obeys the monad laws. -}@@ -144,7 +144,7 @@ -- -- This function can be useful if you are mainly interested in -- mapping a 'Program' to different standard monads, like the state monad.--- For implementing a truly custom monad, +-- For implementing a truly custom monad, -- you should write your interpreter directly with 'view' instead. interpretWithMonad :: forall instr m b. Monad m => (forall a. instr a -> m a) -> (Program instr b -> m b)@@ -187,11 +187,11 @@ i.e. sequences of primitive instructions and actions from the base monad. * The /primitive instructions/ are given by the type constructor @instr :: * -> *@.- + * @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. -}@@ -234,6 +234,19 @@ -> (b -> ProgramT instr m a) -> ProgramViewT instr m a +instance Monad m => Functor (ProgramViewT instr m) where+ fmap f (Return a) = Return $ f a+ fmap f (instr :>>= cont) = instr :>>= (fmap f . cont)++instance Monad m => Applicative (ProgramViewT instr m) where+ pure = return+ (<*>) = ap++instance Monad m => Monad (ProgramViewT instr m) where+ return = Return+ Return a >>= cont = cont a+ (instr :>>= cont1) >>= cont2 = instr :>>= (cont1 >=> unviewT . cont2)+ -- | 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@@ -245,7 +258,7 @@ {-| 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: @@@ -255,7 +268,7 @@ 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)@@ -263,6 +276,47 @@ liftProgram (Instr i) = Instr i +-- | Utility function that extends+-- a given interpretation of instructions as monadic actions+-- to an interpration of 'ProgramT's as monadic actions.+--+-- Ideally, you would not use another monad,+-- but write a custom interpreter directly with `viewT`.+-- See the remark at 'interpretWithMonad'.+interpretWithMonadT :: Monad m => (forall x . instr x -> m x) -> ProgramT instr m a -> m a+interpretWithMonadT interpreter = go+ where+ go program = do+ firstInstruction <- viewT program+ case firstInstruction of+ Return a -> return a+ instruction :>>= continuation -> interpreter instruction >>= (go . continuation)++-- | Utilitiy function for mapping a 'ProgramViewT' back into a 'ProgramT'.+-- +-- Semantically, the function 'unviewT' is an inverse of 'viewT',+-- e.g. we have+--+-- @+-- viewT (singleton i) >>= unviewT = return (singleton i)+-- @+unviewT :: Monad m => ProgramViewT instr m a -> ProgramT instr m a+unviewT (Return a) = return a+unviewT (instruction :>>= continuation) =+ (Instr instruction) `Bind` continuation++-- | Extend a mapping of instructions to a mapping of 'ProgramT'.+mapInstr ::+ forall instr1 instr2 m a . Monad m+ => (forall x . instr1 x -> instr2 x)+ -> ProgramT instr1 m a -> ProgramT instr2 m a+mapInstr f = go+ where+ go :: forall x. ProgramT instr1 m x -> ProgramT instr2 m x+ go (Lift action) = Lift action+ go (Bind action continuation) = Bind (go action) (go . continuation)+ go (Instr instruction) = Instr $ f instruction+ {- $exampleT /Example usage/@@ -290,11 +344,11 @@ {------------------------------------------------------------------------------ mtl instances- + * All of these instances need UndecidableInstances, because they do not satisfy the coverage condition. Most of the instance in the mtl package itself have the same issue.- + * Lifting algebraic operations is easy, lifting control operations is more elaborate, but sometimes possible. See the design notes in `doc/design.md`.@@ -308,8 +362,7 @@ instance (MonadReader r m) => MonadReader r (ProgramT instr m) where ask = lift ask- + local r (Lift m) = Lift (local r m) local r (m `Bind` k) = local r m `Bind` (local r . k) local _ (Instr i) = Instr i-