tardis 0.4.2.0 → 0.4.3.0
raw patch · 9 files changed
+532/−449 lines, 9 filesdep +tardisPVP ok
version bump matches the API change (PVP)
Dependencies added: tardis
API changes (from Hackage documentation)
Files
- Control/Monad/Tardis.hs +0/−128
- Control/Monad/Tardis/Class.hs +0/−108
- Control/Monad/Trans/Tardis.hs +0/−212
- src/Control/Monad/Tardis.hs +128/−0
- src/Control/Monad/Tardis/Class.hs +106/−0
- src/Control/Monad/Trans/Tardis.hs +209/−0
- tardis.cabal +10/−1
- test/Example.hs +69/−0
- test/Main.hs +10/−0
− Control/Monad/Tardis.hs
@@ -1,128 +0,0 @@-{-# OPTIONS_GHC -Wall #-}---- | This module re-exports both 'MonadTardis' and 'TardisT'--- (Wherever there is overlap, the 'MonadTardis' version is preferred.)--- --- The recommended usage of a Tardis is to import this module.-module Control.Monad.Tardis- ( -- * Re-exports- module Control.Monad.Trans.Tardis- , module Control.Monad.Tardis.Class-- -- * What is a Tardis?- -- $whatis- - -- * How do you use a Tardis?- -- $howuse- ) where---import Control.Monad.Tardis.Class-import Control.Monad.Trans.Tardis- ( TardisT- , runTardisT- , evalTardisT- , execTardisT-- , Tardis- , runTardis- , evalTardis- , execTardis- - , noState- )---{- $whatis- A Tardis is the combination of the State monad transformer- and the Reverse State monad transformer.- - The State monad transformer features a forwards-traveling state.- You can retrieve the current value of the state,- and you can set its value, affecting any future attempts- to retrieve it.-- The Reverse State monad transformer is just the opposite:- it features a backwards-traveling state.- You can retrieve the current value of the state,- and you can set its value, affecting any /past/ attempts- to retrieve it. This is a bit weirder than its- forwards-traveling counterpart, so its Monad instance- additionally requires that the underlying Monad it transforms- must be an instance of MonadFix.-- A Tardis is nothing more than mashing these two things together.- A Tardis gives you /two/ states: one which travels /backwards/- (or /upwards/) through your code (referred to as @bw@),- and one which travels /forwards/ (or /downwards/) through your code- (referred to as @fw@). You can retrieve the current- value of either state, and you can set the value of either state.- Setting the forwards-traveling state will affect the /future/,- while setting the backwards-traveling state will affect the /past/.- Take a look at how Monadic bind is implemented for 'TardisT':--> m >>= f = TardisT $ \ ~(bw, fw) -> do-> rec (x, ~(bw'', fw' )) <- runTardisT m (bw', fw)-> (x', ~(bw' , fw'')) <- runTardisT (f x) (bw, fw')-> return (x', (bw'', fw''))-- Like the Reverse State monad transformer, TardisT's Monad instance- requires that the monad it transforms is an instance of MonadFix,- as is evidenced by the use of @rec@.- Notice how the forwards-traveling state travels /normally/:- first it is fed to @m@, producing @fw'@, and then it is fed to @f x@,- producing @fw''@. The backwards-traveling state travels in the opposite- direction: first it is fed to @f x@, producing @bw'@, and then- it is fed to @m@, producing @bw''@.---}--{- $howuse- A Tardis provides four primitive operations,- corresponding to the /get/ and /put/ for each of its two states.- The most concise way to explain it is this:- 'getPast' retrieves the value from the latest 'sendFuture',- while 'getFuture' retrieves the value from the next 'sendPast'.- Beware the pitfall of performing send and get in the wrong order.- Let's consider forwards-traveling state:--> do sendFuture "foo"-> x <- getPast-- In this code snippet, @x@ will be @\"foo\"@, because 'getPast'- grabs the value from the latest 'sendFuture'. If you wanted- to observe that state /before/ overwriting it with @\"foo\"@,- then re-arrange the code so that 'getPast' happens earlier- than 'sendFuture'. Now let's consider backwards-traveling state:--> do x <- getFuture-> sendPast "bar"-- In this code snippet, @x@ will be @\"bar\"@, because 'getFuture'- grabs the value from the next 'sendPast'. If you wanted- to observe that state /before/ overwriting it with @\"bar\"@,- then re-arrange the code so that 'getFuture' happens later- than 'sendPast'.-- TardisT is an instance of MonadFix. This is especially important- when attempting to write backwards-traveling code, because- the name binding occurs later than its usage.- The result of the following code will be @(11, \"Dan Burton\")@.--> flip execTardis (10, "Dan") $ do-> name <- getPast-> sendFuture (name ++ " Burton")-> rec-> sendPast (score + 1)-> score <- getFuture-> return ()-- To avoid using @rec@, you may find 'modifyBackwards' to be useful.- This code is equivalent to the previous example:--> flip execTardis (10, "Dan") $ do-> modifyForwards (++ " Burton")-> modifyBackwards (+ 1)---}-
− Control/Monad/Tardis/Class.hs
@@ -1,108 +0,0 @@-{-# OPTIONS_GHC -Wall -fno-warn-warnings-deprecations #-}-{-# LANGUAGE DoRec #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE FlexibleInstances #-}----- | The class definition of a Tardis,--- as well as a few straightforward combinators--- based on its primitives.--- --- See Control.Monad.Tardis for the general explanation--- of what a Tardis is and how to use it.-module Control.Monad.Tardis.Class- ( -- * The MonadTardis class- MonadTardis (..)- -- * Composite Tardis operations- , modifyForwards- , modifyBackwards- , getsPast- , getsFuture- ) where--import Control.Applicative-import Control.Monad.Fix--import qualified Control.Monad.Trans.Tardis as T---- | A Tardis is parameterized by two state streams:--- a 'backwards-traveling' state and a 'forwards-traveling' state.--- This library consistently puts the backwards-traveling state first--- whenever the two are seen together.--- --- Minimal complete definition:--- ("tardis") or--- ("getPast", "getFuture", "sendPast", and "sendFuture").-class (Applicative m, MonadFix m) => MonadTardis bw fw m | m -> bw, m -> fw where- -- | Retrieve the current value of the 'forwards-traveling' state,- -- which therefore came forwards from the past.- -- You can think of forwards-traveling state as traveling- -- 'downwards' through your code.- getPast :: m fw- - -- | Retrieve the current value of the 'backwards-traveling' state,- -- which therefore came backwards from the future.- -- You can think of backwards-traveling state as traveling- -- 'upwards' through your code.- getFuture :: m bw- - -- | Set the current value of the 'backwards-traveling' state,- -- which will therefore be sent backwards to the past.- -- This value can be retrieved by calls to "getFuture"- -- located 'above' the current location,- -- unless it is overwritten by an intervening "sendPast".- sendPast :: bw -> m ()- - -- | Set the current value of the 'forwards-traveling' state,- -- which will therefore be sent forwards to the future.- -- This value can be retrieved by calls to "getPast"- -- located 'below' the current location,- -- unless it is overwritten by an intervening "sendFuture".- sendFuture :: fw -> m ()-- getPast = tardis $ \ ~(bw, fw) -> (fw, (bw, fw))- getFuture = tardis $ \ ~(bw, fw) -> (bw, (bw, fw))- sendPast bw' = tardis $ \ ~(_bw, fw) -> ((), (bw', fw))- sendFuture fw' = tardis $ \ ~(bw, _fw) -> ((), (bw, fw'))-- -- | A Tardis is merely a pure state transformation.- tardis :: ((bw, fw) -> (a, (bw, fw))) -> m a- tardis f = do- rec- let (a, (future', past')) = f (future, past)- sendPast future'- past <- getPast- future <- getFuture- sendFuture past'- return a---- | Modify the forwards-traveling state--- as it passes through from past to future.-modifyForwards :: MonadTardis bw fw m => (fw -> fw) -> m ()-modifyForwards f = getPast >>= sendFuture . f---- | Modify the backwards-traveling state--- as it passes through from future to past.-modifyBackwards :: MonadTardis bw fw m => (bw -> bw) -> m ()-modifyBackwards f = do- rec- sendPast (f x)- x <- getFuture- return ()---- | Retrieve a specific view of the forwards-traveling state.-getsPast :: MonadTardis bw fw m => (fw -> a) -> m a-getsPast f = f <$> getPast---- | Retrieve a specific view of the backwards-traveling state.-getsFuture :: MonadTardis bw fw m => (bw -> a) -> m a-getsFuture f = f <$> getFuture---instance MonadFix m => MonadTardis bw fw (T.TardisT bw fw m) where- getPast = T.getPast- getFuture = T.getFuture- sendPast = T.sendPast- sendFuture = T.sendFuture- tardis = T.tardis
− Control/Monad/Trans/Tardis.hs
@@ -1,212 +0,0 @@-{-# OPTIONS_GHC -Wall -fno-warn-warnings-deprecations #-}-{-# LANGUAGE DoRec #-}----- | The data definition of a "TardisT"--- as well as its primitive operations,--- and straightforward combinators based on the primitives.--- --- See Control.Monad.Tardis for the general explanation--- of what a Tardis is and how to use it.-module Control.Monad.Trans.Tardis (- -- * The Tardis monad transformer- TardisT (TardisT, runTardisT)- , evalTardisT- , execTardisT-- -- * The Tardis monad- , Tardis- , runTardis- , evalTardis- , execTardis-- -- * Primitive Tardis operations- , tardis-- , getPast- , getFuture- , sendPast- , sendFuture-- -- * Composite Tardis operations- , modifyForwards- , modifyBackwards-- , getsPast- , getsFuture-- -- * Other- , mapTardisT- , noState- ) where--import Control.Applicative-import Control.Monad.Identity-import Control.Monad.Trans-import Control.Monad.Morph----- Definition------------------------------------------------------ | A TardisT is parameterized by two state streams:--- a 'backwards-traveling' state and a 'forwards-traveling' state.--- This library consistently puts the backwards-traveling state first--- whenever the two are seen together.-newtype TardisT bw fw m a = TardisT- { runTardisT :: (bw, fw) -> m (a, (bw, fw))- -- ^ A TardisT is merely an effectful state transformation- }---- | Using a Tardis with no monad underneath--- will prove to be most common use case.--- Practical uses of a TardisT require that the--- underlying monad be an instance of MonadFix,--- but note that the IO instance of MonadFix--- is almost certainly unsuitable for use with--- Tardis code.-type Tardis bw fw = TardisT bw fw Identity---- | A Tardis is merely a pure state transformation.-runTardis :: Tardis bw fw a -> (bw, fw) -> (a, (bw, fw))-runTardis m = runIdentity . runTardisT m----- Helpers------------------------------------------------------ | Run a Tardis, and discard the final state,--- observing only the resultant value.-evalTardisT :: Monad m => TardisT bw fw m a -> (bw, fw) -> m a-evalTardisT t s = fst `liftM` runTardisT t s---- | Run a Tardis, and discard the resultant value,--- observing only the final state (of both streams).--- Note that the 'final' state of the backwards-traveling state--- is the state it reaches by traveling from the 'bottom'--- of your code to the 'top'.-execTardisT :: Monad m => TardisT bw fw m a -> (bw, fw) -> m (bw, fw)-execTardisT t s = snd `liftM` runTardisT t s----- | Run a Tardis, and discard the final state,--- observing only the resultant value.-evalTardis :: Tardis bw fw a -> (bw, fw) -> a-evalTardis t = runIdentity . evalTardisT t---- | Run a Tardis, and discard the resultant value,--- observing only the final state (of both streams).-execTardis :: Tardis bw fw a -> (bw, fw) -> (bw, fw)-execTardis t = runIdentity . execTardisT t----- | A function that operates on the internal representation of a Tardis--- can also be used on a Tardis.-mapTardisT :: (m (a, (bw, fw)) -> n (b, (bw, fw)))- -> TardisT bw fw m a -> TardisT bw fw n b-mapTardisT f m = TardisT $ f . runTardisT m---- | Some Tardises never observe the 'initial' state--- of either state stream, so it is convenient--- to simply hand dummy values to such Tardises.--- --- > noState = (undefined, undefined)-noState :: (a, b)-noState = (undefined, undefined)----- Instances----------------------------------------------------instance MonadFix m => Monad (TardisT bw fw m) where- return x = tardis $ \s -> (x, s)- m >>= f = TardisT $ \ ~(bw, fw) -> do- rec (x, ~(bw'', fw' )) <- runTardisT m (bw', fw)- (x', ~(bw' , fw'')) <- runTardisT (f x) (bw, fw')- return (x', (bw'', fw''))--instance MonadFix m => Functor (TardisT bw fw m) where- fmap = liftM--instance MonadFix m => Applicative (TardisT bw fw m) where- pure = return- (<*>) = ap---instance MonadTrans (TardisT bw fw) where- lift m = TardisT $ \s -> do- x <- m- return (x, s)--instance MonadFix m => MonadFix (TardisT bw fw m) where- mfix f = TardisT $ \s -> do- rec (x, s') <- runTardisT (f x) s- return (x, s')--instance MFunctor (TardisT bw fw) where- hoist f = mapTardisT f---- Basics------------------------------------------------------ | From a stateful computation, construct a Tardis.--- This is the pure parallel to the constructor "TardisT",--- and is polymorphic in the transformed monad.-tardis :: Monad m => ((bw, fw) -> (a, (bw, fw))) -> TardisT bw fw m a-tardis f = TardisT $ \s -> return (f s)---- | Retrieve the current value of the 'forwards-traveling' state,--- which therefore came forwards from the past.--- You can think of forwards-traveling state as traveling--- 'downwards' through your code.-getPast :: Monad m => TardisT bw fw m fw-getPast = tardis $ \ ~(bw, fw) -> (fw, (bw, fw))---- | Retrieve the current value of the 'backwards-traveling' state,--- which therefore came backwards from the future.--- You can think of backwards-traveling state as traveling--- 'upwards' through your code.-getFuture :: Monad m => TardisT bw fw m bw-getFuture = tardis $ \ ~(bw, fw) -> (bw, (bw, fw))---- | Set the current value of the 'backwards-traveling' state,--- which will therefore be sent backwards to the past.--- This value can be retrieved by calls to "getFuture"--- located 'above' the current location,--- unless it is overwritten by an intervening "sendPast".-sendPast :: Monad m => bw -> TardisT bw fw m ()-sendPast bw' = tardis $ \ ~(_bw, fw) -> ((), (bw', fw))---- | Set the current value of the 'forwards-traveling' state,--- which will therefore be sent forwards to the future.--- This value can be retrieved by calls to "getPast"--- located 'below' the current location,--- unless it is overwritten by an intervening "sendFuture".-sendFuture :: Monad m => fw -> TardisT bw fw m ()-sendFuture fw' = tardis $ \ ~(bw, _fw) -> ((), (bw, fw'))----- | Modify the forwards-traveling state--- as it passes through from past to future.-modifyForwards :: MonadFix m => (fw -> fw) -> TardisT bw fw m ()-modifyForwards f = getPast >>= sendFuture . f---- | Modify the backwards-traveling state--- as it passes through from future to past.-modifyBackwards :: MonadFix m => (bw -> bw) -> TardisT bw fw m ()-modifyBackwards f = do- rec- sendPast (f x)- x <- getFuture- return ()----- | Retrieve a specific view of the forwards-traveling state.-getsPast :: MonadFix m => (fw -> a) -> TardisT bw fw m a-getsPast f = fmap f getPast----- | Retrieve a specific view of the backwards-traveling state.-getsFuture :: MonadFix m => (bw -> a) -> TardisT bw fw m a-getsFuture f = fmap f getFuture-
+ src/Control/Monad/Tardis.hs view
@@ -0,0 +1,128 @@+{-# OPTIONS_GHC -Wall #-}++-- | This module re-exports both 'MonadTardis' and 'TardisT'+-- (Wherever there is overlap, the 'MonadTardis' version is preferred.)+-- +-- The recommended usage of a Tardis is to import this module.+module Control.Monad.Tardis+ ( -- * Re-exports+ module Control.Monad.Trans.Tardis+ , module Control.Monad.Tardis.Class++ -- * What is a Tardis?+ -- $whatis+ + -- * How do you use a Tardis?+ -- $howuse+ ) where+++import Control.Monad.Tardis.Class+import Control.Monad.Trans.Tardis+ ( TardisT+ , runTardisT+ , evalTardisT+ , execTardisT++ , Tardis+ , runTardis+ , evalTardis+ , execTardis+ + , noState+ )+++{- $whatis+ A Tardis is the combination of the State monad transformer+ and the Reverse State monad transformer.+ + The State monad transformer features a forwards-traveling state.+ You can retrieve the current value of the state,+ and you can set its value, affecting any future attempts+ to retrieve it.++ The Reverse State monad transformer is just the opposite:+ it features a backwards-traveling state.+ You can retrieve the current value of the state,+ and you can set its value, affecting any /past/ attempts+ to retrieve it. This is a bit weirder than its+ forwards-traveling counterpart, so its Monad instance+ additionally requires that the underlying Monad it transforms+ must be an instance of MonadFix.++ A Tardis is nothing more than mashing these two things together.+ A Tardis gives you /two/ states: one which travels /backwards/+ (or /upwards/) through your code (referred to as @bw@),+ and one which travels /forwards/ (or /downwards/) through your code+ (referred to as @fw@). You can retrieve the current+ value of either state, and you can set the value of either state.+ Setting the forwards-traveling state will affect the /future/,+ while setting the backwards-traveling state will affect the /past/.+ Take a look at how Monadic bind is implemented for 'TardisT':++> m >>= f = TardisT $ \ ~(bw, fw) -> do+> rec (x, ~(bw'', fw' )) <- runTardisT m (bw', fw)+> (x', ~(bw' , fw'')) <- runTardisT (f x) (bw, fw')+> return (x', (bw'', fw''))++ Like the Reverse State monad transformer, TardisT's Monad instance+ requires that the monad it transforms is an instance of MonadFix,+ as is evidenced by the use of @rec@.+ Notice how the forwards-traveling state travels /normally/:+ first it is fed to @m@, producing @fw'@, and then it is fed to @f x@,+ producing @fw''@. The backwards-traveling state travels in the opposite+ direction: first it is fed to @f x@, producing @bw'@, and then+ it is fed to @m@, producing @bw''@.++-}++{- $howuse+ A Tardis provides four primitive operations,+ corresponding to the /get/ and /put/ for each of its two states.+ The most concise way to explain it is this:+ 'getPast' retrieves the value from the latest 'sendFuture',+ while 'getFuture' retrieves the value from the next 'sendPast'.+ Beware the pitfall of performing send and get in the wrong order.+ Let's consider forwards-traveling state:++> do sendFuture "foo"+> x <- getPast++ In this code snippet, @x@ will be @\"foo\"@, because 'getPast'+ grabs the value from the latest 'sendFuture'. If you wanted+ to observe that state /before/ overwriting it with @\"foo\"@,+ then re-arrange the code so that 'getPast' happens earlier+ than 'sendFuture'. Now let's consider backwards-traveling state:++> do x <- getFuture+> sendPast "bar"++ In this code snippet, @x@ will be @\"bar\"@, because 'getFuture'+ grabs the value from the next 'sendPast'. If you wanted+ to observe that state /before/ overwriting it with @\"bar\"@,+ then re-arrange the code so that 'getFuture' happens later+ than 'sendPast'.++ TardisT is an instance of MonadFix. This is especially important+ when attempting to write backwards-traveling code, because+ the name binding occurs later than its usage.+ The result of the following code will be @(11, \"Dan Burton\")@.++> flip execTardis (10, "Dan") $ do+> name <- getPast+> sendFuture (name ++ " Burton")+> rec+> sendPast (score + 1)+> score <- getFuture+> return ()++ To avoid using @rec@, you may find 'modifyBackwards' to be useful.+ This code is equivalent to the previous example:++> flip execTardis (10, "Dan") $ do+> modifyForwards (++ " Burton")+> modifyBackwards (+ 1)++-}+
+ src/Control/Monad/Tardis/Class.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE RecursiveDo #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE FlexibleInstances #-}++-- | The class definition of a Tardis,+-- as well as a few straightforward combinators+-- based on its primitives.+--+-- See Control.Monad.Tardis for the general explanation+-- of what a Tardis is and how to use it.+module Control.Monad.Tardis.Class+ ( -- * The MonadTardis class+ MonadTardis (..)+ -- * Composite Tardis operations+ , modifyForwards+ , modifyBackwards+ , getsPast+ , getsFuture+ ) where++import Control.Applicative+import Control.Monad.Fix++import qualified Control.Monad.Trans.Tardis as T++-- | A Tardis is parameterized by two state streams:+-- a 'backwards-traveling' state and a 'forwards-traveling' state.+-- This library consistently puts the backwards-traveling state first+-- whenever the two are seen together.+-- +-- Minimal complete definition:+-- ("tardis") or+-- ("getPast", "getFuture", "sendPast", and "sendFuture").+class (Applicative m, MonadFix m) => MonadTardis bw fw m | m -> bw, m -> fw where+ -- | Retrieve the current value of the 'forwards-traveling' state,+ -- which therefore came forwards from the past.+ -- You can think of forwards-traveling state as traveling+ -- 'downwards' through your code.+ getPast :: m fw+ + -- | Retrieve the current value of the 'backwards-traveling' state,+ -- which therefore came backwards from the future.+ -- You can think of backwards-traveling state as traveling+ -- 'upwards' through your code.+ getFuture :: m bw+ + -- | Set the current value of the 'backwards-traveling' state,+ -- which will therefore be sent backwards to the past.+ -- This value can be retrieved by calls to "getFuture"+ -- located 'above' the current location,+ -- unless it is overwritten by an intervening "sendPast".+ sendPast :: bw -> m ()+ + -- | Set the current value of the 'forwards-traveling' state,+ -- which will therefore be sent forwards to the future.+ -- This value can be retrieved by calls to "getPast"+ -- located 'below' the current location,+ -- unless it is overwritten by an intervening "sendFuture".+ sendFuture :: fw -> m ()++ getPast = tardis $ \ ~(bw, fw) -> (fw, (bw, fw))+ getFuture = tardis $ \ ~(bw, fw) -> (bw, (bw, fw))+ sendPast bw' = tardis $ \ ~(_bw, fw) -> ((), (bw', fw))+ sendFuture fw' = tardis $ \ ~(bw, _fw) -> ((), (bw, fw'))++ -- | A Tardis is merely a pure state transformation.+ tardis :: ((bw, fw) -> (a, (bw, fw))) -> m a+ tardis f = do+ rec+ let (a, (future', past')) = f (future, past)+ sendPast future'+ past <- getPast+ future <- getFuture+ sendFuture past'+ return a++-- | Modify the forwards-traveling state+-- as it passes through from past to future.+modifyForwards :: MonadTardis bw fw m => (fw -> fw) -> m ()+modifyForwards f = getPast >>= sendFuture . f++-- | Modify the backwards-traveling state+-- as it passes through from future to past.+modifyBackwards :: MonadTardis bw fw m => (bw -> bw) -> m ()+modifyBackwards f = do+ rec+ sendPast (f x)+ x <- getFuture+ return ()++-- | Retrieve a specific view of the forwards-traveling state.+getsPast :: MonadTardis bw fw m => (fw -> a) -> m a+getsPast f = f <$> getPast++-- | Retrieve a specific view of the backwards-traveling state.+getsFuture :: MonadTardis bw fw m => (bw -> a) -> m a+getsFuture f = f <$> getFuture+++instance MonadFix m => MonadTardis bw fw (T.TardisT bw fw m) where+ getPast = T.getPast+ getFuture = T.getFuture+ sendPast = T.sendPast+ sendFuture = T.sendFuture+ tardis = T.tardis
+ src/Control/Monad/Trans/Tardis.hs view
@@ -0,0 +1,209 @@+{-# LANGUAGE RecursiveDo #-}++-- | The data definition of a "TardisT"+-- as well as its primitive operations,+-- and straightforward combinators based on the primitives.+--+-- See Control.Monad.Tardis for the general explanation+-- of what a Tardis is and how to use it.+module Control.Monad.Trans.Tardis (+ -- * The Tardis monad transformer+ TardisT (TardisT, runTardisT)+ , evalTardisT+ , execTardisT++ -- * The Tardis monad+ , Tardis+ , runTardis+ , evalTardis+ , execTardis++ -- * Primitive Tardis operations+ , tardis++ , getPast+ , getFuture+ , sendPast+ , sendFuture++ -- * Composite Tardis operations+ , modifyForwards+ , modifyBackwards++ , getsPast+ , getsFuture++ -- * Other+ , mapTardisT+ , noState+ ) where++import Control.Applicative+import Control.Monad.Identity+import Control.Monad.Trans+import Control.Monad.Morph+++-- Definition+-------------------------------------------------++-- | A TardisT is parameterized by two state streams:+-- a 'backwards-traveling' state and a 'forwards-traveling' state.+-- This library consistently puts the backwards-traveling state first+-- whenever the two are seen together.+newtype TardisT bw fw m a = TardisT+ { runTardisT :: (bw, fw) -> m (a, (bw, fw))+ -- ^ A TardisT is merely an effectful state transformation+ }++-- | Using a Tardis with no monad underneath+-- will prove to be most common use case.+-- Practical uses of a TardisT require that the+-- underlying monad be an instance of MonadFix,+-- but note that the IO instance of MonadFix+-- is almost certainly unsuitable for use with+-- Tardis code.+type Tardis bw fw = TardisT bw fw Identity++-- | A Tardis is merely a pure state transformation.+runTardis :: Tardis bw fw a -> (bw, fw) -> (a, (bw, fw))+runTardis m = runIdentity . runTardisT m+++-- Helpers+-------------------------------------------------++-- | Run a Tardis, and discard the final state,+-- observing only the resultant value.+evalTardisT :: Monad m => TardisT bw fw m a -> (bw, fw) -> m a+evalTardisT t s = fst `liftM` runTardisT t s++-- | Run a Tardis, and discard the resultant value,+-- observing only the final state (of both streams).+-- Note that the 'final' state of the backwards-traveling state+-- is the state it reaches by traveling from the 'bottom'+-- of your code to the 'top'.+execTardisT :: Monad m => TardisT bw fw m a -> (bw, fw) -> m (bw, fw)+execTardisT t s = snd `liftM` runTardisT t s+++-- | Run a Tardis, and discard the final state,+-- observing only the resultant value.+evalTardis :: Tardis bw fw a -> (bw, fw) -> a+evalTardis t = runIdentity . evalTardisT t++-- | Run a Tardis, and discard the resultant value,+-- observing only the final state (of both streams).+execTardis :: Tardis bw fw a -> (bw, fw) -> (bw, fw)+execTardis t = runIdentity . execTardisT t+++-- | A function that operates on the internal representation of a Tardis+-- can also be used on a Tardis.+mapTardisT :: (m (a, (bw, fw)) -> n (b, (bw, fw)))+ -> TardisT bw fw m a -> TardisT bw fw n b+mapTardisT f m = TardisT $ f . runTardisT m++-- | Some Tardises never observe the 'initial' state+-- of either state stream, so it is convenient+-- to simply hand dummy values to such Tardises.+-- +-- > noState = (undefined, undefined)+noState :: (a, b)+noState = (undefined, undefined)+++-- Instances+-------------------------------------------------++instance MonadFix m => Monad (TardisT bw fw m) where+ return x = tardis $ \s -> (x, s)+ m >>= f = TardisT $ \ ~(bw, fw) -> do+ rec (x, ~(bw'', fw' )) <- runTardisT m (bw', fw)+ (x', ~(bw' , fw'')) <- runTardisT (f x) (bw, fw')+ return (x', (bw'', fw''))++instance MonadFix m => Functor (TardisT bw fw m) where+ fmap = liftM++instance MonadFix m => Applicative (TardisT bw fw m) where+ pure = return+ (<*>) = ap+++instance MonadTrans (TardisT bw fw) where+ lift m = TardisT $ \s -> do+ x <- m+ return (x, s)++instance MonadFix m => MonadFix (TardisT bw fw m) where+ mfix f = TardisT $ \s -> do+ rec (x, s') <- runTardisT (f x) s+ return (x, s')++instance MFunctor (TardisT bw fw) where+ hoist f = mapTardisT f++-- Basics+-------------------------------------------------++-- | From a stateful computation, construct a Tardis.+-- This is the pure parallel to the constructor "TardisT",+-- and is polymorphic in the transformed monad.+tardis :: Monad m => ((bw, fw) -> (a, (bw, fw))) -> TardisT bw fw m a+tardis f = TardisT $ \s -> return (f s)++-- | Retrieve the current value of the 'forwards-traveling' state,+-- which therefore came forwards from the past.+-- You can think of forwards-traveling state as traveling+-- 'downwards' through your code.+getPast :: Monad m => TardisT bw fw m fw+getPast = tardis $ \ ~(bw, fw) -> (fw, (bw, fw))++-- | Retrieve the current value of the 'backwards-traveling' state,+-- which therefore came backwards from the future.+-- You can think of backwards-traveling state as traveling+-- 'upwards' through your code.+getFuture :: Monad m => TardisT bw fw m bw+getFuture = tardis $ \ ~(bw, fw) -> (bw, (bw, fw))++-- | Set the current value of the 'backwards-traveling' state,+-- which will therefore be sent backwards to the past.+-- This value can be retrieved by calls to "getFuture"+-- located 'above' the current location,+-- unless it is overwritten by an intervening "sendPast".+sendPast :: Monad m => bw -> TardisT bw fw m ()+sendPast bw' = tardis $ \ ~(_bw, fw) -> ((), (bw', fw))++-- | Set the current value of the 'forwards-traveling' state,+-- which will therefore be sent forwards to the future.+-- This value can be retrieved by calls to "getPast"+-- located 'below' the current location,+-- unless it is overwritten by an intervening "sendFuture".+sendFuture :: Monad m => fw -> TardisT bw fw m ()+sendFuture fw' = tardis $ \ ~(bw, _fw) -> ((), (bw, fw'))+++-- | Modify the forwards-traveling state+-- as it passes through from past to future.+modifyForwards :: MonadFix m => (fw -> fw) -> TardisT bw fw m ()+modifyForwards f = getPast >>= sendFuture . f++-- | Modify the backwards-traveling state+-- as it passes through from future to past.+modifyBackwards :: MonadFix m => (bw -> bw) -> TardisT bw fw m ()+modifyBackwards f = do+ rec+ sendPast (f x)+ x <- getFuture+ return ()+++-- | Retrieve a specific view of the forwards-traveling state.+getsPast :: MonadFix m => (fw -> a) -> TardisT bw fw m a+getsPast f = fmap f getPast+++-- | Retrieve a specific view of the backwards-traveling state.+getsFuture :: MonadFix m => (bw -> a) -> TardisT bw fw m a+getsFuture f = fmap f getFuture
tardis.cabal view
@@ -1,5 +1,5 @@ name: tardis-version: 0.4.2.0+version: 0.4.3.0 synopsis: Bidirectional state monad transformer homepage: https://github.com/DanBurton/tardis bug-reports: https://github.com/DanBurton/tardis/issues@@ -22,6 +22,7 @@ library default-language: Haskell2010+ hs-source-dirs: src exposed-modules: Control.Monad.Tardis , Control.Monad.Tardis.Class , Control.Monad.Trans.Tardis@@ -30,6 +31,14 @@ , mtl==2.* , mmorph==1.* +test-suite tardis-tests+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends: base >= 4.8 && < 5+ , tardis+ other-modules: Example source-repository head type: git
+ test/Example.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE RecursiveDo #-}++module Example where++import Control.Monad.Tardis++data BowlingGame = BowlingGame+ { frames :: ![Frame] -- should be 9, too tedious to type restrict+ , lastFrame :: LFrame }++data Frame = Strike+ | Spare { firstThrow :: !Int }+ | Frame { firstThrow, secondThrow :: !Int }++data LFrame = LStrike { bonus1, bonus2 :: !Int }+ | LSpare { throw1, bonus1 :: !Int }+ | LFrame { throw1, throw2 :: !Int }++sampleGame :: BowlingGame+sampleGame = BowlingGame+ { frames =+ [ Strike , Spare 9+ , Strike , Strike+ , Strike , Frame 8 1+ , Spare 7 , Strike+ , Strike+ ]+ , lastFrame = LStrike 10 10+ }++newtype PreviousScores = PreviousScores [Int]+newtype NextThrows = NextThrows (Int, Int)++toScores :: BowlingGame -> [Int]+toScores game = flip evalTardis initState $ go (frames game) where+ go :: [Frame] -> Tardis NextThrows PreviousScores [Int]+ go [] = do+ PreviousScores scores <- getPast+ let score = head scores+ return $ (finalFrameScore + score) : scores+ go (f : fs) = do+ rec+ sendPast $ NextThrows throws'+ PreviousScores scores <- getPast+ let score = head scores+ sendFuture $ PreviousScores (score' : scores)+ NextThrows ~(nextThrow1, nextThrow2) <- getFuture+ let (score', throws') = case f of+ Strike -> (score + 10 + nextThrow1 + nextThrow2, (10, nextThrow1))+ Spare n -> (score + 10 + nextThrow1, (n, 10 - n))+ Frame n m -> (score + n + m, (n, m))+ go fs++ finalFrameScore = case lastFrame game of+ LStrike n m -> 10 + n + m+ LSpare _n m -> 10 + m+ LFrame n m -> n + m++ initState = (NextThrows $ case lastFrame game of+ LStrike n _m -> (10, n)+ LSpare n _m -> (n, 10 - n)+ LFrame n m -> (n, m)+ , PreviousScores [0])++expectedScores :: [Int]+expectedScores = [236,206,176,146,126,117,98,70,40,20,0]++actualScores :: [Int]+actualScores = toScores sampleGame
+ test/Main.hs view
@@ -0,0 +1,10 @@+import Example+import System.Exit++main :: IO ()+main = case actualScores == expectedScores of+ False -> do+ putStrLn $ "Expected: " <> show expectedScores+ putStrLn $ "Actual: " <> show actualScores+ exitFailure+ True -> exitSuccess