auto 0.2.0.4 → 0.2.0.5
raw patch · 5 files changed
+213/−94 lines, 5 filesdep +MonadRandomPVP: minor bump suggested
API additions: PVP suggests at least a minor version bump
Dependencies added: MonadRandom
API changes (from Hackage documentation)
+ Control.Auto.Process.Random: bernoulliMR :: MonadRandom m => Double -> Auto m a (Blip a)
+ Control.Auto.Process.Random: randIntervalsMR :: MonadRandom m => Double -> Interval m a a
+ Control.Auto.Process.Random: sealRandom :: (RandomGen g, Serialize g, Monad m) => Auto (RandT g m) a b -> g -> Auto m a b
+ Control.Auto.Process.Random: sealRandomStd :: Monad m => Auto (RandT StdGen m) a b -> StdGen -> Auto m a b
+ Control.Auto.Process.Random: sealRandom_ :: (RandomGen g, Serialize g, Monad m) => Auto (RandT g m) a b -> g -> Auto m a b
Files
- CHANGELOG.md +8/−0
- auto.cabal +5/−3
- src/Control/Auto/Collection.hs +8/−4
- src/Control/Auto/Effects.hs +27/−19
- src/Control/Auto/Process/Random.hs +165/−68
CHANGELOG.md view
@@ -1,3 +1,11 @@+0.2.0.5+-------+<https://github.com/mstksg/auto/releases/tag/v0.2.0.5>++* **Control.Auto.Process.Random**: Added combinators and sealers dealing+ for working with an underlying `Rand` or `RandT` monad.+* Because of this, committed to adding `MonadRandom` as a dependency.+ 0.2.0.4 ------- <https://github.com/mstksg/auto/releases/tag/v0.2.0.4>
auto.cabal view
@@ -1,5 +1,5 @@ name: auto-version: 0.2.0.4+version: 0.2.0.5 synopsis: Denotative, locally stateful programming DSL & platform description: (Up to date documentation is maintained at <https://mstksg.github.com/auto>)@@ -34,8 +34,9 @@ repository on github for plenty of real-world and toy examples to learn from! .- Support available on freenode's #haskell, and also on- the github's issue tracker.+ Support available on freenode's #haskell-auto,+ #haskell-game, and also on the github issue+ tracker for the source repository. . Import "Control.Auto" to begin! @@ -84,6 +85,7 @@ , random >= 1.1 && < 2.0 , semigroups >= 0.16.2.2 && < 0.17 , transformers >= 0.4.2.0 && < 0.5+ , MonadRandom >= 0.3.0.1 && < 0.4 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall
src/Control/Auto/Collection.hs view
@@ -317,8 +317,10 @@ -- using this can inspire more disciplined usage. Also works as a drop-in -- replacement for 'dynZipF'. dynZipF_ :: Monad m- => (k -> Interval m a b)- -> a+ => (k -> Interval m a b) -- ^ function to generate a new+ -- 'Interval' for each coming @k@+ -- in the blip stream.+ -> a -- ^ "default" input to feed in -> Auto m ([a], Blip [k]) [b] dynZipF_ f x0 = go [] where@@ -433,8 +435,10 @@ -- using this can inspire more disciplined usage. Also works as a drop-in -- replacement for 'dynMapF'. dynMapF_ :: Monad m- => (k -> Interval m a b)- -> a+ => (k -> Interval m a b) -- ^ function to generate a new+ -- 'Interval' for each coming @k@+ -- in the blip stream.+ -> a -- ^ "default" input to feed in -> Auto m (IntMap a, Blip [k]) (IntMap b) dynMapF_ f x0 = go 0 IM.empty IM.empty where
src/Control/Auto/Effects.hs view
@@ -242,7 +242,9 @@ -- @foo@ be written using 'State', and being able to use it in a program -- with no global state? ----- Using 'sealState'!+-- Using 'sealState'! Write the part of your program that would like+-- shared global state with 'State'...and compose it with the rest as if it+-- doesn't, locking it away! -- -- @ -- sealState :: Auto (State s) a b -> s -> Auto' a b@@ -263,13 +265,9 @@ -- stream is the result of running the stream through @f@, first with an -- initial state of @s0@, and afterwards with each next updated state. ----- This can be extended to sealing 'RandT' from the /MonadRandom/ package--- as well, as long as you 'hoistA' first with @'StateT' . 'runRandT'@.------ sealState :: (Monad m, Serialize s)- => Auto (StateT s m) a b- -> s+ => Auto (StateT s m) a b -- ^ 'Auto' run over 'State'+ -> s -- ^ initial state -> Auto m a b sealState a s0 = mkAutoM (sealState <$> resumeAuto a <*> get) (saveAuto a *> put s0)@@ -279,8 +277,8 @@ -- | The non-resuming/non-serializing version of 'sealState'. sealState_ :: Monad m- => Auto (StateT s m) a b- -> s+ => Auto (StateT s m) a b -- ^ 'Auto' run over 'State'+ -> s -- ^ initial state -> Auto m a b sealState_ a s0 = mkAutoM (sealState_ <$> resumeAuto a <*> pure s0) (saveAuto a)@@ -288,12 +286,12 @@ ((y, a'), s1) <- runStateT (stepAuto a x) s0 return (y, sealState_ a' s1) --- | Turns an @a -> 'StateT' s m b@ arrow into an @'Auto' m a b@, when--- given an initial state. Will continually "run the function", using the--- state returned from the last run.+-- | Turns an @a -> 'StateT' s m b@ Kleisli arrow into an @'Auto' m a b@,+-- when given an initial state. Will continually "run the function", using+-- the state returned from the last run. fromState :: (Serialize s, Monad m)- => (a -> StateT s m b)- -> s+ => (a -> StateT s m b) -- ^ 'State' arrow+ -> s -- ^ initial state -> Auto m a b fromState st = mkStateM (runStateT . st) @@ -301,15 +299,15 @@ -- serialized/resumed, so every time the 'Auto' is resumed, it starts over -- with the given initial state. fromState_ :: Monad m- => (a -> StateT s m b)- -> s+ => (a -> StateT s m b) -- ^ 'State' arrow+ -> s -- ^ initial state -> Auto m a b fromState_ st = mkStateM_ (runStateT . st) -- | "Unrolls" the underlying @'WriterT' w m@ 'Monad', so that an 'Auto' -- that takes in a stream of @a@ and outputs a stream of @b@ will now--- output a stream @(b, w)@, where @w@ is the accumulated log of the--- underlying 'Writer' at every step.+-- output a stream @(b, w)@, where @w@ is the "new log" of the underlying+-- 'Writer' at every step. -- -- @ -- foo :: Auto (Writer (Sum Int)) Int Int@@ -328,7 +326,17 @@ -- a list of outputs and a "final accumulator state" of 10, for stepping it -- ten times. ----- We can write and compose own 'Auto's under 'Writer', using the+-- However, if we use 'runWriterA' before streaming it, we get:+--+-- >>> let fooW = runWriterA foo+-- >>> streamAuto' fooW [1..10]+-- [ (1 , Sum 2), (3 , Sum 2), (6 , Sum 2)+-- , (10, Sum 2), (15, Sum 2), (21, Sum 2), -- ...+--+-- Instead of accumulating it between steps, we get to "catch" the 'Writer'+-- output at every individual step.+--+-- We can write and compose our own 'Auto's under 'Writer', using the -- convenience of a shared accumulator, and then "use them" with other -- 'Auto's: --
src/Control/Auto/Process/Random.hs view
@@ -8,17 +8,21 @@ -- Portability : portable -- -- This module provides 'Auto's (purely) generating entropy in the form of--- random or noisy processes. Note that every 'Auto' here is completely--- deterministic --- given the same initial seed, one would expect the same--- stream of outputs on every run. Furthermore, if a serializable 'Auto'--- is serialized and resumed, it will continue along the deterministic path+-- random or noisy processes, as well as 'Auto's to purify/seal+-- 'Auto's with underlying entropy.+--+-- Note that every 'Auto' and combinator here is completely deterministic+-- --- given the same initial seed, one would expect the same stream of+-- outputs on every run. Furthermore, if a serializable 'Auto' is+-- serialized and resumed, it will continue along the deterministic path -- dictated by the /original/ seed given. ----- All of these 'Auto's come in three flavors: one serializing one that--- works with any serializable 'RandomGen' instance, one serializing one--- that works specifically with 'StdGen' from "System.Random", and one that--- takes any 'RandomGen' (including 'StdGen') and runs it without the--- ability to serialize and resume deterministically.+-- All of these 'Auto's and combinators come in three flavors: one+-- serializing one that works with any serializable 'RandomGen' instance,+-- one serializing one that works specifically with 'StdGen' from+-- "System.Random", and one that takes any 'RandomGen' (including 'StdGen')+-- and runs it without the ability to serialize and resume+-- deterministically. -- -- The reason why there's a specialized 'StdGen' version for all of these -- is that 'StdGen' actually doesn't have a 'Serialize' instance, so a@@ -37,7 +41,7 @@ -- @ -- -- These are useful for generating noise...a new random value at every--- stoep. They are entropy sources.+-- step. They are entropy sources. -- -- Alternatively, if you want to give up parallelizability and determinism -- and have your entire 'Auto' be sequential, you can make your entire@@ -65,24 +69,17 @@ -- you re-load your 'Auto'. Also, 'Auto''s are parallelizable, while -- @'Auto' ('Rand' g)@s are not. ----- As a compromise, you can then "seal" away the stateful part with--- 'sealState' and 'hoistA':------ @--- sealRandom :: 'Monad' m => 'Auto' ('RandT' g m) a b -> g -> 'Auto' m a b--- sealRandom a0 = 'sealState' . 'hoistA' ('StateT' . 'runRandT')------ sealRandom' :: 'Auto' ('Rand' g) a b -> g -> 'Auto'' a b--- sealRandom' = sealRandom--- @------ Where 'hoistA' turns an @'Auto' ('RandT' g m)@ into an @'Auto' m@.+-- As a compromise, you can then "seal" away the underlying monad with+-- 'sealRandom', which takes an @'Auto' ('RandT' g m) a b@, a starting @g@,+-- and turns it into a normal @'Auto' m a b@, with no underlying randomness+-- monad. -- -- In this way, you can run any 'Auto' under 'Rand' or 'RandT' as if it was--- a normal 'Auto' "without" underlying randomness. (These functions--- aren't given here so that this library doesn't incurr a dependency on--- /MonadRandom/). This lets you compose your sequential/non-parallel parts--- in 'Rand' and use it as a part of an 'Auto''.+-- a normal 'Auto' "without" underlying randomness. This lets you compose+-- your sequential/non-parallel parts in 'Rand'...and the later, use it as+-- a part of a parallelizable/potentially non-sequential 'Auto''. It's+-- also convenient because you don't have to manually split and pass around+-- seeds to every 'Auto' that requires entropy. -- -- The other generators given are for useful random processes you might run -- into. The first is a 'Blip' stream that emits at random times with the@@ -116,19 +113,30 @@ , randIntervals , stdRandIntervals , randIntervals_+ -- * Underlying entropy monads+ -- ** Sealers+ , sealRandom+ , sealRandomStd+ , sealRandom_+ -- ** Processes+ , bernoulliMR+ , randIntervalsMR ) where import Control.Applicative import Control.Auto.Blip import Control.Auto.Blip.Internal import Control.Auto.Core+import Control.Auto.Effects import Control.Auto.Interval import Control.Category+import Control.Monad (guard)+import Control.Monad.Random+import Control.Monad.Trans.State (StateT(..)) import Data.Bits import Data.Serialize import Data.Tuple import Prelude hiding (id, (.), concat, concatMap, sum)-import System.Random -- | Given a seed-consuming generating function of form @g -> (b, g)@ -- (where @g@ is the seed, and @b@ is the result) and an initial seed,@@ -173,7 +181,7 @@ -- rands :: (Serialize g, RandomGen g) => (g -> (b, g)) -- ^ random generating function- -> g -- ^ initial generator+ -> g -- ^ random generator seed -> Auto m a b rands r = mkState (\_ g -> g `seq` r g) {-# INLINE rands #-}@@ -182,10 +190,10 @@ -- that you can serialize and resume. This is needed because 'StdGen' -- doesn't have a 'Serialize' instance. ----- See the documentation of 'rands' for more information.+-- See the documentation of 'rands' for more information on this 'Auto'. -- stdRands :: (StdGen -> (b, StdGen)) -- ^ random generating function- -> StdGen -- ^ initial generator+ -> StdGen -- ^ random generator seed -> Auto m a b stdRands r = mkState' (read <$> get) (put . show) (\_ g -> r g) {-# INLINE stdRands #-}@@ -194,7 +202,7 @@ -- | The non-serializing/non-resuming version of 'rands'. rands_ :: RandomGen g => (g -> (b, g)) -- ^ random generating function- -> g -- ^ initial generator+ -> g -- ^ random generator seed -> Auto m a b rands_ r = mkState_ (\_ g -> r g) {-# INLINE rands_ #-}@@ -214,8 +222,8 @@ -- @ -- randsM :: (Serialize g, RandomGen g, Monad m)- => (g -> m (b, g))- -> g+ => (g -> m (b, g)) -- ^ (monadic) random generating function+ -> g -- ^ random generator seed -> Auto m a b randsM r = mkStateM (\_ g -> r g) {-# INLINE randsM #-}@@ -224,19 +232,19 @@ -- that you can serialize and resume. This is needed because 'StdGen' -- doesn't have a 'Serialize' instance. ----- See the documentation of 'randsM' for more information.+-- See the documentation of 'randsM' for more information on this 'Auto'. -- stdRandsM :: Monad m- => (StdGen -> m (b, StdGen))- -> StdGen+ => (StdGen -> m (b, StdGen)) -- ^ (monadic) random generating function+ -> StdGen -- ^ random generator seed -> Auto m a b stdRandsM r = mkStateM' (read <$> get) (put . show) (\_ g -> r g) {-# INLINE stdRandsM #-} -- | The non-serializing/non-resuming version of 'randsM'. randsM_ :: (RandomGen g, Monad m)- => (g -> m (b, g))- -> g+ => (g -> m (b, g)) -- ^ (monadic) random generating function+ -> g -- ^ random generator seed -> Auto m a b randsM_ r = mkStateM_ (\_ g -> r g) {-# INLINE randsM_ #-}@@ -263,8 +271,8 @@ -- -- (This is basically 'mkState', specialized.) arrRand :: (Serialize g, RandomGen g)- => (a -> g -> (b, g))- -> g+ => (a -> g -> (b, g)) -- ^ random arrow+ -> g -- ^ random generator seed -> Auto m a b arrRand = mkState @@ -280,8 +288,8 @@ -- ('runRandT' .) :: 'RandomGen' g => (a -> 'RandT' g b) -> (a -> g -> m (b, g)) -- @ arrRandM :: (Monad m, Serialize g, RandomGen g)- => (a -> g -> m (b, g))- -> g+ => (a -> g -> m (b, g)) -- ^ (monadic) random arrow+ -> g -- ^ random generator seed -> Auto m a b arrRandM = mkStateM @@ -289,10 +297,10 @@ -- that you can serialize and resume. This is needed because 'StdGen' -- doesn't have a 'Serialize' instance. ----- See the documentation of 'arrRand' for more information.+-- See the documentation of 'arrRand' for more information on this 'Auto'. ---arrRandStd :: (a -> StdGen -> (b, StdGen))- -> StdGen+arrRandStd :: (a -> StdGen -> (b, StdGen)) -- ^ random arrow+ -> StdGen -- ^ random generator seed -> Auto m a b arrRandStd = mkState' (read <$> get) (put . show) @@ -300,24 +308,24 @@ -- that you can serialize and resume. This is needed because 'StdGen' -- doesn't have a 'Serialize' instance. ----- See the documentation of 'arrRandM' for more information.+-- See the documentation of 'arrRandM' for more information on this 'Auto'. ---arrRandStdM :: (a -> StdGen -> m (b, StdGen))- -> StdGen+arrRandStdM :: (a -> StdGen -> m (b, StdGen)) -- ^ (mondic) random arrow+ -> StdGen -- ^ random generator seed -> Auto m a b arrRandStdM = mkStateM' (read <$> get) (put . show) -- | The non-serializing/non-resuming version of 'arrRand'. arrRand_ :: RandomGen g- => (a -> g -> (b, g))- -> g+ => (a -> g -> (b, g)) -- ^ random arrow+ -> g -- ^ random generator seed -> Auto m a b arrRand_ = mkState_ -- | The non-serializing/non-resuming version of 'arrRandM'. arrRandM_ :: RandomGen g- => (a -> g -> m (b, g))- -> g+ => (a -> g -> m (b, g)) -- ^ (monadic) random arrow+ -> g -- ^ random generator seed -> Auto m a b arrRandM_ = mkStateM_ @@ -335,8 +343,8 @@ -- on average once every @1/p@ ticks. -- bernoulli :: (Serialize g, RandomGen g)- => Double -- ^ probability of success per step- -> g -- ^ initial seed+ => Double -- ^ probability of any step emitting+ -> g -- ^ random generator seed -> Auto m a (Blip a) bernoulli p = mkState (_bernoulliF p) @@ -344,17 +352,20 @@ -- so that you can serialize and resume. This is needed because 'StdGen' -- doesn't have a 'Serialize' instance. ----- See the documentation of 'bernoulli' for more information.+-- See the documentation of 'bernoulli' for more information on this+-- 'Auto'. -- stdBernoulli :: Double -- ^ probability of any step emitting- -> StdGen -- ^ initial seed+ -> StdGen -- ^ random generator seed+ -- (between 0 and 1) -> Auto m a (Blip a) stdBernoulli p = mkState' (read <$> get) (put . show) (_bernoulliF p) -- | The non-serializing/non-resuming version of 'bernoulli'. bernoulli_ :: RandomGen g => Double -- ^ probability of any step emitting- -> g -- ^ initial seed+ -> g -- ^ random generator seed+ -- (between 0 and 1) -> Auto m a (Blip a) bernoulli_ p = mkState_ (_bernoulliF p) @@ -365,10 +376,29 @@ -> (Blip a, g) _bernoulliF p x g = (outp, g') where- (roll, g') = randomR (0, 1 :: Double) g+ (roll, g') = randomR (0, 1) g outp | roll <= p = Blip x | otherwise = NoBlip +-- | 'bernoulli', but uses an underlying entropy source ('MonadRandom')+-- to get its randomness from, instead of an initially passed seed.+--+-- You can recover exactly @'bernoulli' p@ by using @'sealRandom'+-- ('bernoulliMR' p)@.+--+-- See 'sealRandom' for more information.+bernoulliMR :: MonadRandom m+ => Double -- ^ probability of any step emiting+ -- (between 0 and 1)+ -> Auto m a (Blip a)+bernoulliMR p = arrM $ \x -> do+ roll <- getRandomR (0, 1)+ return $ if roll <= p+ then Blip x+ else NoBlip+-- TODO: rewrite for AMP++ -- | An 'Interval' that is "on" and "off" for contiguous but random -- intervals of time...when "on", allows values to pass as "on" ('Just'), -- but when "off", suppresses all incoming values (outputing 'Nothing').@@ -386,8 +416,8 @@ -- parameter of @1 / l@. -- randIntervals :: (Serialize g, RandomGen g)- => Double- -> g+ => Double -- ^ expected length of on/off intervals+ -> g -- ^ random generator seed -> Interval m a a randIntervals l = mkState (_randIntervalsF (1/l)) . swap . random @@ -395,10 +425,11 @@ -- "System.Random", so that you can serialize and resume. This is needed -- because 'StdGen' doesn't have a 'Serialize' instance. ----- See the documentation of 'randIntervals' for more information.+-- See the documentation of 'randIntervals' for more information on this+-- 'Auto'. ---stdRandIntervals :: Double- -> StdGen+stdRandIntervals :: Double -- ^ expected length of on/off intervals+ -> StdGen -- ^ random generator seed -> Interval m a a stdRandIntervals l = mkState' (read <$> get) (put . show)@@ -407,8 +438,8 @@ -- | The non-serializing/non-resuming version of 'randIntervals'. randIntervals_ :: RandomGen g- => Double- -> g+ => Double -- ^ expected length of on/off intervals+ -> g -- ^ random generator seed -> Interval m a a randIntervals_ l = mkState_ (_randIntervalsF (1/l)) . swap . random @@ -419,9 +450,75 @@ -> (Maybe a, (g, Bool)) _randIntervalsF thresh x (g, onoff) = (outp, (g', onoff')) where- (roll, g') = randomR (0, 1 :: Double) g+ (roll, g') = randomR (0, 1) g onoff' = onoff `xor` (roll <= thresh)- outp | onoff = Just x- | otherwise = Nothing+ outp = x <$ guard onoff -- should this be onoff' ? +-- | 'randIntervals', but uses an underlying entropy source ('MonadRandom')+-- to get its randomness from, instead of an initially passed seed.+--+-- You can recover exactly @'randIntervals' l@ by using @'sealRandom'+-- ('randIntervalsMR' l)@.+--+-- See 'sealRandom' for more information.+--+randIntervalsMR :: MonadRandom m+ => Double -- ^ expected length of on/off intervals+ -> Interval m a a+randIntervalsMR l = flip mkStateM Nothing $ \x monoff -> do+ onoff <- case monoff of+ Just oo -> return oo+ Nothing -> getRandom+ roll <- getRandomR (0, 1)+ let onoff' = onoff `xor` (roll <= thresh)+ return (x <$ guard onoff, Just onoff')+ where+ thresh = 1/l++-- | Takes an 'Auto' over an 'Rand' or 'RandT' underlying monad as an+-- entropy source, and "seals it away" to just be a normal 'Auto' or+-- 'Auto'':+--+-- @+-- 'sealRandom' :: 'Auto' ('Rand' g) a b -> g -> 'Auto'' a b+-- @+--+-- You can now compose your entropic 'Auto' with other 'Auto's (using '.',+-- and other combinators) as if it were a normal 'Auto'.+--+-- Useful because you can create entire programs that have access to an+-- underlying entropy souce by composing with 'Rand'...and then, at the end+-- of it all, use/compose it with normal 'Auto's as if it were a "pure"+-- 'Auto'.+sealRandom :: (RandomGen g, Serialize g, Monad m)+ => Auto (RandT g m) a b -- ^ 'Auto' to seal+ -> g -- ^ initial seed+ -> Auto m a b+sealRandom a = sealState (hoistA (StateT . runRandT) a)++-- | The non-serializing/non-resuming version of 'sealRandom_'. The random+-- seed is not re-loaded/resumed, so every time you resume, the stream of+-- available randomness begins afresh.+sealRandom_ :: (RandomGen g, Serialize g, Monad m)+ => Auto (RandT g m) a b -- ^ 'Auto' to seal+ -> g -- ^ initial seed+ -> Auto m a b+sealRandom_ a = sealState_ (hoistA (StateT . runRandT) a)++-- | Like 'sealRandom', but specialized for 'StdGen' from "System.Random",+-- so that you can serialize and resume. This is needed because 'StdGen'+-- doesn't have a 'Serialize' instance.+--+-- See the documentation of 'sealRandom' for more information on this+-- combinator.+--+sealRandomStd :: Monad m+ => Auto (RandT StdGen m) a b -- ^ 'Auto' to seal+ -> StdGen -- ^ initial seed+ -> Auto m a b+sealRandomStd a g0 = mkAutoM (sealRandomStd <$> resumeAuto a <*> (read <$> get))+ (saveAuto a *> put (show g0))+ $ \x -> do+ ((y, a'), g1) <- runRandT (stepAuto a x) g0+ return (y, sealRandomStd a' g1)