{-# LANGUAGE ViewPatterns #-} -- | -- Module : Control.Auto.Blip -- Description : Tools for generating and manipulating blip streams. -- Copyright : (c) Justin Le 2015 -- License : MIT -- Maintainer : justin@jle.im -- Stability : unstable -- Portability : portable -- -- -- This module provides tools for generating and manipulating "blip -- streams". The blip stream abstraction is not fundamental to 'Auto', but -- rather, like /interval/, is a very useful semantic tool for the -- denotation of many programs, games, simulations, and computations in -- general that you are likely to write with this library. -- module Control.Auto.Blip ( -- * 'Blip' -- $blip -- * The Blip type Blip , perBlip -- ** Merging , merge , mergeL , mergeR , mergeLs , mergeRs , foldrB , foldlB' -- ** Blip stream creation (dangerous!) , emitJusts , emitOn , onJusts -- ** Blip stream collapse , fromBlips , fromBlipsWith , holdWith , holdWith_ -- * Step/"time" based Blip streams and generators , never , immediately , inB , every , eachAt , eachAt_ -- * Modifying Blip streams , tagBlips , modifyBlips , (<&) , (&>) , once , notYet , lagBlips , lagBlips_ , filterB , joinB , mapMaybeB , takeB , takeWhileB , dropB , dropWhileB -- * Scanning & Accumulating Blip streams , accumB , accumB_ , scanB , scanB_ , mscanB , mscanB_ , countB -- * Blips on edges , onChange , onChange_ , became , became_ , became' , noLonger , noLonger_ , noLonger' , onFlip , onFlip_ , onFlip' ) where import Control.Applicative import Control.Arrow import Control.Auto.Blip.Internal import Control.Auto.Core import Control.Category import Data.Monoid import Data.Profunctor import Data.List import Data.Serialize import Prelude hiding ((.), id, sequence) infixr 5 <& infixl 5 &> -- $blip -- -- In the context of inputs/outputs of 'Auto', a @'Blip' a@ represents -- a "blip stream" that occasionally, in isolated incidents, emits a value -- of type @a@. -- -- For example, @'Auto'' a ('Blip' b)@ is an 'Auto'' that a stream of @a@'s -- as input and outputs a *blip stream* that occasionally emits with a @b@. -- An @'Auto'' ('Blip' a) b@ is an 'Auto'' that takes a *blip stream* that -- occasionally emits with a @a@ and outputs a stream of @b@'s. -- -- If an 'Auto' takes or outputs a "blip stream", it comes with some -- "semantic" contracts on to how the stream behaves. The main contract is -- that your blip stream should only output on (meaningfully) "isolated" -- incidents, and never on continuous regions of the input stream. -- -- By this, we mean that every emitted value is (conceptually) emitted -- "alone", and not as a part of continuous on/off chunks. -- -- == Example situations -- -- A good example would be, say, a blip stream that emits every time -- a user/player sends a certain type of command. Or a blip stream that -- emits every time a slowly-moving value crosses over from positive to -- negative. -- -- A bad example would be a blip stream that emits when a player /doesn't/ -- send a certain less-common type of command. Or a blip stream that emits -- whenever a slowly-moving value /is/ positive or negative. -- -- == Contrast with /Intervals/ -- -- Blip streams are contrasted with another semantic tool: stream -- _intervals_, manipulated with "Control.Auto.Interval". /Intervals/ are -- adjacent/contiguous "chunks" of on/off behavior, and are on or off for -- contiguous "chunks" at a time. So when deciding whether or not you want -- to use the semantics of blip streams or the semantics of /Interval/, -- consider: is this behavior going to be "on/off" for chunks at a time -- (such as an interval that is on whenever a slowly-moving value is -- positive)? Or is it something that is usually "not on", but makes -- separate, isolated, "blips" --- each emitted value alone and -- (semantically) isolated from the rest. -- -- == Motivations -- -- The main motivations of the semantic concept of blip streams (and why they -- even exist in the first place) is probably for how well they integrate -- with /Interval/ semantics and, with intervals, the various powerful -- switching combinators from "Control.Auto.Switch". Many of the -- combinators in that module are designed so that switches can be -- "triggered" by blip stream emissions. -- -- Blip streams have many usages, as will be explained later. You'll also -- find that blip streams work well with their cousins, /interval/ streams. -- But perhaps the use case that stands out above all (and is alone enough -- to motivate their existence) is in switching. -- -- == "Blip semantics" -- -- We say that a blip stream has "blip semantics" when it is used in -- a way that its emitted values are "isolated", "alone", "discrete", in -- this way. When it is not, we say that the stream "breaks" blip -- semantics. -- -- Note that this can't really be enforced by the types, so if you're -- a library or framework developer, it's up to you to take care that the -- blip streams you offer all conform to blip semantics. However, if -- you're just making an application, you can use most of the combinators -- in this library/module and not worry. -- -- Also note that in many of these cases, "blip semantics" depends on how -- the 'Auto's are /composed/, and what they are composed to. If the value -- in question is "almost always" positive and only negative at isolated -- points in time, then such a "blip stream that emits whenever the value -- is negative" has proper blip semantics. If the value in question is -- slowly-moving and meandering, and might spend a lot of time negative at -- a time, then the same blip stream would /not/ preserve blip semantics. -- -- === Why semantics are important -- -- Why should you care? I can't tell you want to do, right? -- -- Well, for the same reason that typeclasses like 'Eq', 'Functor', and -- 'Monad' have laws. Yeah, you can make any instance you want that -- satisfies the types. But almost all of the usefulness of those -- typeclasses comes from our ability to "reason" about the behavior of -- their instances, and to be able to develop an intuition about their -- usage. We would be surprised if we had an 'Eq' instance where @x == x@ -- and @x /= x@ are both true...and it would completely break down any -- attempt at understanding what 'Eq' code "means". -- -- You can think of "blip semantics" as being the "laws" of blip streams. -- If we assume that things follow blip semantics properly, then we can -- reason about them in a unified and useful way. If we can trust that -- blip streams actually behave "like blip streams", then blip streams -- become an extremely useful tool for denoting certain behaviors and -- programs. -- -- If we can't...then it becomes a lot less useful :) -- -- In particular, one big use case for blip streams (the switching -- mechanisms "Control.Auto.Switch") all only "work well" when your blip -- streams follow proper semantics. -- -- === Combinators preserve semantics -- -- /Most/ of the combinators in this module try their best to preserve blip -- semantics. That is, you can't use them in a way that will produce -- a non-semantic-abiding blip stream. You can "trust" them, and if you -- use only safe combinators, you don't ever have to worry. Well. That -- much, at least. -- -- There are a few notable exceptions: -- -- * 'every', 'eachAt', 'eachAt_', when you pass in an interval of 1. -- * 'onChange', when the input value isn't ever expected to stay the same -- between steps. -- * 'emitOn', 'emitJusts', 'onJusts', in the cases mentioned in the -- documentation for 'emitOn'. -- -- -- == Practical examples -- -- There are many practical examples of using blip streams in the various -- examples in <https://github.com/mstksg/auto-examples auto-examples>, -- especially from /chatbot/. There, blip streams are used in many -- situations, primarily streams for players sending certain commands. It's -- also used in /hangman/, to signify player events such as victory, -- good/bad guesses, etc. -- -- Blip streams work very closely with the various switching combinators in -- "Control.Auto.Switch". If anything, if there is only one reason to use -- blip streams, it's with the various switching mechanisms in that module. -- All of the switching combinators rely on the fact that your blip streams -- follow proper semantics, further emphasizing the importance of -- conforming to the semantics. -- -- == For library, framework, and back-end developers -- -- Remember that this module is only meant to export "safe" combinators -- that try their best to maintain blip semantics. Think of this module -- as a useful guideline to help programmers maintain semantics at -- compile-time, by only exporting not-as-dangerous combinators. -- -- However, all of these rules are for the denotation of your /program -- logic/. These rules are for the benefit of reasoning about the behavior -- of your program at the logic level. -- -- As a library or framework or back-end developer, however, you aren't -- programming at the logic level, but rather at the gritty implementation -- level. So, you might want to provide blip streams and for your -- library users or application developers or the game logic you are -- writing. -- -- For this, you might find the hidden constructors and tools in -- "Control.Auto.Blip.Internal" helpful, and there is more information at -- the documentation for that module. -- -- | Merge all the blip streams together into one, favoring the first -- emitted value. mergeLs :: [Blip a] -> Blip a mergeLs = foldr mergeL NoBlip -- | Merge all the blip streams together into one, favoring the last -- emitted value. mergeRs :: [Blip a] -> Blip a mergeRs = foldl' mergeR NoBlip -- | Merge all of the blip streams together, using the given merging -- function associating from the right. foldrB :: (a -> a -> a) -> a -> [Blip a] -> Blip a foldrB f b0 = foldr (merge f) (Blip b0) -- | Merge all of the blip streams together, using the given merging -- function associating from the left. foldlB' :: (a -> a -> a) -> a -> [Blip a] -> Blip a foldlB' f b0 = foldl' (merge f) (Blip b0) -- | Takes two 'Auto's producing blip streams and returns a "merged" -- 'Auto' that emits when either of the original 'Auto's emit. When both -- emit at the same time, the left (first) one is favored. -- -- prop> a1 <& a2 == mergeL <$> a1 <*> a2 (<&) :: Monad m => Auto m a (Blip b) -> Auto m a (Blip b) -> Auto m a (Blip b) (<&) = liftA2 mergeL -- | Takes two 'Auto's producing blip streams and returns a "merged" -- 'Auto' that emits when either of the original 'Auto's emit. When both -- emit at the same time, the right (second) one is favored. -- -- prop> a1 &> a2 == mergeR <$> a1 <*> a2 (&>) :: Monad m => Auto m a (Blip b) -> Auto m a (Blip b) -> Auto m a (Blip b) (&>) = liftA2 mergeR -- | An 'Auto' that ignores its input and produces a blip stream never -- emits. never :: Auto m a (Blip b) never = mkConst NoBlip -- | Produces a blip stream that emits with the first received input value, -- and never again after that. -- -- Often used with 'pure': -- -- > immediately . pure "Emit me!" -- -- Or, in proc notation: -- -- > blp <- immediately -< "Emit me!" -- -- to get a blip stream that emits a given value (eg., "Emit me!") once -- and stops emitting ever again. -- -- >>> streamAuto' (immediately . pure "Emit me!") [1..5] -- [Blip "Emit Me!", NoBlip, NoBlip, NoBlip, NoBlip] -- immediately :: Auto m a (Blip a) immediately = mkState f False where f _ True = (NoBlip, True) f x False = (Blip x, True) -- | Produces a blip stream that only emits once, with the input value on -- the given step number. It emits the input /on/ that many steps. -- -- prop> immediately == inB 1 inB :: Int -- ^ number of steps before value is emitted. -> Auto m a (Blip a) inB n = mkState f (n, False) where f _ (_, True ) = (NoBlip, (1 , True )) f x (i, False) | i <= 1 = (Blip x, (1 , True )) | otherwise = (NoBlip, (i-1, False)) -- | Produces a blip stream that emits the input value whenever the input -- satisfies a given predicate. -- -- Warning! This 'Auto' has the capability of "breaking" blip semantics. -- Be sure you know what you are doing when using this. Blip streams are -- semantically supposed to only emit at discrete, separate occurrences. -- Do not use this for interval-like (on and off for chunks at a time) -- things; each input should be dealt with as a separate thing. -- -- For interval semantics, we have 'Interval' from "Control.Auto.Interval". -- -- Good example: -- -- > -- is only emitting at discrete blips -- > emitOn even . iterator (+ 1) 0 -- -- Bad examples: -- -- > -- is emitting for "durations" or "intervals" of time. -- > emitOn (< 10) . iterator (+ 1) 0 -- > -- > emitOn (const True) . foo -- -- These bad examples would be good use cases of 'Interval'. -- emitOn :: (a -> Bool) -- ^ predicate to emit on -> Auto m a (Blip a) emitOn p = mkFunc $ \x -> if p x then Blip x else NoBlip -- | An 'Auto' that runs every input through a @a -> 'Maybe' b@ test and -- produces a blip stream that emits the value inside every 'Just' result. -- -- Particularly useful with prisms from the /lens/ package, where things -- like @emitJusts (preview _Right)@ will emit the @b@ whenever the input -- @Either a b@ stream is a @Right@. -- -- Warning! Carries all of the same dangers of 'emitOn'. You can easily -- break blip semantics with this if you aren't sure what you are doing. -- Remember to only emit at discrete, separate occurences, and not for -- interval-like (on and off for chunks at a time) things. For interval -- semantics, we have "Control.Auto.Interval". -- -- See the examples of 'emitOn' for more concrete good/bad use cases. emitJusts :: (a -> Maybe b) -- ^ "predicate" to emit on. -> Auto m a (Blip b) emitJusts p = mkFunc (maybe NoBlip Blip . p) -- | @'every' n@ is an 'Auto' that emits with the incoming inputs on every -- @n@th input value. First emitted value is on the @n@th step. -- -- Will obviously break blip semantics when you pass in 1. -- every :: Int -- ^ emit every @n@ steps. -> Auto m a (Blip a) every (max 1 -> n) = mkState f n where f x i | i <= 1 = (Blip x, n ) | otherwise = (NoBlip, i - 1) -- | @'eachAt' n xs@ is an 'Auto' that ignores its input and creates -- a blip stream that emits each element of @xs@ one at a time, evey @n@ -- steps. First emitted value is at step @n@. -- -- Once the list is exhausted, never emits again. -- -- Obviously breaks blip semantics when you pass in 1. -- -- The process of serializing and resuming this 'Auto' is O(n) space and -- time with the length of @xs@. So don't serialize this if you plan on -- passing an infinite list :) See "Control.Auto.Generate" for more -- options. -- -- prop> eachAt n xs == perBlip (fromList xs) . every n eachAt :: Serialize b => Int -- ^ emit every @n@ steps -> [b] -- ^ list to emit values from -> Auto m a (Blip b) eachAt (max 1 -> n) xs = mkState (\_ -> _eachAtF n) (n, xs) -- | The non-serializing/non-resumable version of 'eachAt'. eachAt_ :: Int -- ^ emit every @n@ steps -> [b] -- ^ list to emit values from -> Auto m a (Blip b) eachAt_ (max 1 -> n) xs = mkState_ (\_ -> _eachAtF n) (n, xs) _eachAtF :: Int -> (Int, [b]) -> (Blip b, (Int, [b])) _eachAtF n (i, xs) = case xs of [] -> (NoBlip, (0 , xs)) y:ys | i <= 1 -> (Blip y, (n , ys)) | otherwise -> (NoBlip, (i - 1, xs)) -- | Suppress all upstream emissions when the predicate (on the emitted -- value) fails. filterB :: (a -> Bool) -- ^ filtering predicate -> Auto m (Blip a) (Blip a) filterB p = mkFunc $ \x -> case x of Blip x' | p x' -> x _ -> NoBlip -- | "Collapses" a blip stream of blip streams into single blip stream. -- that emits whenever the inner-nested stream emits. joinB :: Auto m (Blip (Blip a)) (Blip a) joinB = mkFunc (blip NoBlip id) -- | Applies the given function to every emitted value, and suppresses all -- those for which the result is 'Nothing'. Otherwise, lets it pass -- through with the value in the 'Just'. mapMaybeB :: (a -> Maybe b) -> Auto m (Blip a) (Blip b) mapMaybeB f = mkFunc $ \x -> case x of Blip x' -> maybe NoBlip Blip $ f x' _ -> NoBlip -- | Supress all upstream emitted values except for the very first. once :: Auto m (Blip a) (Blip a) once = mkState f False where f _ True = (NoBlip, True ) f e@(Blip _) False = (e, True ) f _ False = (NoBlip, False) -- | Suppress only the first emission coming from upstream, and let all the -- others pass uninhibited. notYet :: Auto m (Blip a) (Blip a) notYet = mkState f False where f e True = (e , True ) f (Blip _) False = (NoBlip, True ) f _ False = (NoBlip, False) -- | @'takeB' n@ allows only the first @n@ emissions to pass; it suppresses -- all of the rest. takeB :: Int -- ^ number of emissions to allow to pass -> Auto m (Blip a) (Blip a) takeB = mkState f . max 0 where f _ 0 = (NoBlip, 0 ) f e@(Blip _) i = (e , i-1) f _ i = (NoBlip, i ) -- | Allow all emitted valuesto pass until the first that fails the -- predicate. takeWhileB :: (a -> Bool) -- ^ filtering predicate -> Auto m (Blip a) (Blip a) takeWhileB p = mkState f False where f _ True = (NoBlip, True ) f e@(Blip x) False | p x = (e , False) f _ False = (NoBlip, True ) -- | @'dropB' n@ suppresses the first @n@ emissions from upstream and -- passes through the rest uninhibited. dropB :: Int -- ^ number of emissions to suppress initially -> Auto m (Blip a) (Blip a) dropB = mkState f . max 0 where f x 0 = (x , 0 ) f (Blip _) i = (NoBlip, i-1) f _ i = (NoBlip, i ) -- | Suppress all emited values until the first one satisfying the -- predicate, then allow the rest to pass through. dropWhileB :: (a -> Bool) -- ^ filtering predicate -> Auto m (Blip a) (Blip a) dropWhileB p = mkState f False where f e True = (e , True ) f e@(Blip x) False | p x = (NoBlip, False) | otherwise = (e , True ) f _ False = (NoBlip, False) -- | Takes in a blip stream and outputs a blip stream where each emission -- is delayed/lagged by one step. -- -- >>> streamAuto' (emitOn (\x -> x `mod` 3 == 0)) [1..9] -- >>> [NoBlip, NoBlip, Blip 3, NoBlip, NoBlip, Blip 6, NoBlip, NoBlip, Blip 9] -- >>> streamAuto' (lagBlips . emitOn (\x -> x `mod` 3 == 0)) [1..9] -- >>> [NoBlip, NoBlip, NoBlip, Blip 3, NoBlip, NoBlip, Blip 6, NoBlip, NoBlip] -- lagBlips :: Serialize a => Auto m (Blip a) (Blip a) lagBlips = mkState (\x s -> (s, x)) NoBlip -- | The non-serializing/non-resuming version of 'lagBlips'. lagBlips_ :: Auto m (Blip a) (Blip a) lagBlips_ = mkState_ (\x s -> (s, x)) NoBlip -- | Accumulates all emissions in the incoming blip stream with -- a "folding function", with a given starting value. @b -> a -> b@, with -- a starting @b@, gives @'Auto' m ('Blip' a) ('Blip' b)@. -- -- The resulting blip stream will emit every time the input stream emits, -- but with the "accumulated value". -- -- Basically 'accum', but on blip stream emissions. -- -- prop> accumB f x0 == perBlip (accum f x0) accumB :: Serialize b => (b -> a -> b) -- ^ folding function -> b -- ^ initial value -> Auto m (Blip a) (Blip b) accumB f = mkState (_accumBF f) -- | The non-serializing/non-resuming version of 'accumB'. accumB_ :: (b -> a -> b) -- ^ folding function -> b -- ^ initial value -> Auto m (Blip a) (Blip b) accumB_ f = mkState_ (_accumBF f) _accumBF :: (b -> a -> b) -> Blip a -> b -> (Blip b, b) _accumBF f e y0 = case e of Blip x -> let y1 = f y0 x in (Blip y1, y1) NoBlip -> (NoBlip , y0) -- | The output is the result of folding up every emitted value seen thus -- far, with the given folding function and initial value. -- -- prop> scanB f x0 == holdWith x0 . accumB f x0 -- -- >>> let a = scanB (+) 0 . eachAt 2 [1,2,3] -- >>> take 8 . streamAuto' a $ repeat () -- [0, 1, 1, 3, 3, 6, 6, 6, 6] scanB :: Serialize b => (b -> a -> b) -- ^ folding function -> b -- ^ initial value -> Auto m (Blip a) b scanB f = accum (_scanBF f) -- | The non-serializing/non-resuming version of 'scanB'. scanB_ :: (b -> a -> b) -> b -- ^ folding function -> Auto m (Blip a) b -- ^ initial value scanB_ f = accum_ (_scanBF f) _scanBF :: (b -> a -> b) -> b -> Blip a -> b _scanBF f y0 = blip y0 (f y0) -- | The output is the 'mconcat' (monoid sum) of all emitted values seen -- this far. mscanB :: (Monoid a, Serialize a) => Auto m (Blip a) a mscanB = scanB (<>) mempty -- | The non-serializing/non-resuming version of 'mscanB'. mscanB_ :: Monoid a => Auto m (Blip a) a mscanB_ = scanB_ (<>) mempty -- | The output is the number of emitted values received from the upstream -- blip stream so far. countB :: Auto m (Blip a) Int countB = accum (\i -> (i +) . blip 0 (const 1)) 0 -- | Blip stream that emits whenever the predicate applied to the input -- switches from false to true. Emits with the triggering input value. became :: Serialize a => (a -> Bool) -- ^ change condition -> Auto m a (Blip a) became p = accum (_becameF p) NoBlip -- | Blip stream that emits whenever the predicate applied to the input -- switches from true to false. Emits with the triggering input value. noLonger :: Serialize a => (a -> Bool) -- ^ change condition -> Auto m a (Blip a) noLonger p = became (not . p) -- | Blip stream that emits whenever the predicate applied to the input -- switches from true to false or false to true. Emits with the triggering -- input value. onFlip :: (Serialize a, Monad m) => (a -> Bool) -- ^ change condition -> Auto m a (Blip a) onFlip p = became p &> noLonger p -- | The non-serializing/non-resumable version of 'became'. became_ :: Monad m => (a -> Bool) -- ^ change condition -> Auto m a (Blip a) became_ p = accum_ (_becameF p) NoBlip -- | The non-serializing/non-resumable version of 'noLonger'. noLonger_ :: Monad m => (a -> Bool) -- ^ change condition -> Auto m a (Blip a) noLonger_ p = became_ (not . p) -- | The non-serializing/non-resumable version of 'onFlip'. onFlip_ :: Monad m => (a -> Bool) -- ^ change condition -> Auto m a (Blip a) onFlip_ p = became_ p &> noLonger_ p _becameF :: (a -> Bool) -> Blip a -> a -> Blip a _becameF p e x | p x = blip (Blip x) (const NoBlip) e | otherwise = NoBlip -- | Like 'became', but emits a '()' instead of the triggering input value. -- -- Useful because it can be serialized without the output needing -- a 'Serialize' instance. became' :: Monad m => (a -> Bool) -- ^ change condition -> Auto m a (Blip ()) became' p = accum f NoBlip where f e x | p x = blip (Blip ()) (const NoBlip) e | otherwise = NoBlip -- | Like 'noLonger', but emits a '()' instead of the triggering input -- value. -- -- Useful because it can be serialized without the output needing -- a 'Serialize' instance. noLonger' :: Monad m => (a -> Bool) -- ^ change condition -> Auto m a (Blip ()) noLonger' p = became' (not . p) -- | Like 'onFlip', but emits a '()' instead of the triggering input value. -- -- Useful because it can be serialized without the output needing -- a 'Serialize' instance. onFlip' :: Monad m => (a -> Bool) -- ^ change condition -> Auto m a (Blip Bool) onFlip' p = fmap (True <$) (became' p) &> fmap (False <$) (noLonger' p) -- | Blip stream that emits whenever the input value changes. Emits with -- the new value. -- -- Warning: Note that, when composed on a value that is never expected to -- keep the same value twice, this technically breaks blip semantics. onChange :: (Serialize a, Eq a) => Auto m a (Blip a) onChange = mkState _onChangeF Nothing -- | The non-serializing/non-resumable version of 'onChange'. onChange_ :: Eq a => Auto m a (Blip a) onChange_ = mkState_ _onChangeF Nothing _onChangeF :: Eq a => a -> Maybe a -> (Blip a, Maybe a) _onChangeF x Nothing = (NoBlip, Just x ) _onChangeF x (Just x') | x == x' = (NoBlip, Just x') | otherwise = (Blip x, Just x ) -- | An 'Auto' that emits whenever it receives a 'Just' input, with the -- value inside the 'Just'. -- -- Warning! Carries all of the same dangers of 'emitOn'. You can easily -- break blip semantics with this if you aren't sure what you are doing. -- Remember to only emit at discrete, separate occurences, and not for -- interval-like (on and off for chunks at a time) things. For interval -- semantics, we have "Control.Auto.Interval". -- -- See the examples of 'emitOn' for more concrete good/bad use cases. onJusts :: Auto m (Maybe a) (Blip a) onJusts = mkFunc (maybe NoBlip Blip) -- | @'fromBlips' d@ is an 'Auto' that decomposes the incoming blip -- stream by constantly outputting @d@ except when the stream emits, and -- outputs the emitted value when it does. fromBlips :: a -- ^ the "default value" to output when the input is not -- emitting. -> Auto m (Blip a) a fromBlips d = mkFunc (blip d id) -- | @'fromBlipsWith' d f@ is an 'Auto' that decomposes the incoming blip -- stream by constantly outputting @d@ except when the stream emits, and -- outputs the result of applying @f@ to the emitted value when it does. fromBlipsWith :: b -- ^ the 'default value" to output when the input is not -- emitting. -> (a -> b) -- ^ the function to apply to the emitted value -- whenever input is emitting. -> Auto m (Blip a) b fromBlipsWith d f = mkFunc (blip d f) -- | @'holdWith' y0@ is an 'Auto' whose output is always the /most recently -- emitted/ value from the input blip stream. Before anything is emitted, -- @y0@ is outputted as a placeholder. -- -- Contrast with 'hold' from "Control.Auto.Interval". holdWith :: Serialize a => a -> Auto m (Blip a) a holdWith = accum f where f x = blip x id -- | A non-serializing/non-resumable version of 'holdWith'. holdWith_ :: a -> Auto m (Blip a) a holdWith_ = accum_ f where f x = blip x id -- | Re-emits every emission from the input blip stream, but replaces its -- value with the given value. -- -- prop> tagBlips x == modifyBlips (const x) tagBlips :: b -- ^ value to replace every emitted value with -> Auto m (Blip a) (Blip b) tagBlips y = mkFunc (y <$) -- | Re-emits every emission from the input blip stream, but applies the -- given function to the emitted value. modifyBlips :: (a -> b) -- ^ function to modify emitted values with -> Auto m (Blip a) (Blip b) modifyBlips f = mkFunc (fmap f) -- | Takes an @'Auto' m a b@ (an 'Auto' that turns incoming @a@s into -- outputting @b@s) into an @'Auto' m ('Blip' a) ('Blip' b)@; the original -- 'Auto' is lifted to only be applied to emitted contents of a blip -- stream. -- -- When the stream emits, the original 'Auto' is "stepped" with the emitted -- value; when it does not, it is paused and frozen until the next -- emission. -- -- >>> let sums = perBlip (sumFrom 0) -- >>> let blps = eachAt 2 [1,5,2] -- >>> take 8 . streamAuto' blps $ repeat () -- [NoBlip, Blip 1, NoBlip, Blip 5, NoBlip, Blip 2, NoBlip, NoBlip] -- >>> take 8 . streamAuto' (sums . blps) $ repeat () -- [NoBlip, Blip 1, NoBlip, Blip 6, NoBlip, Blip 8, NoBlip, NoBlip] -- perBlip :: Monad m => Auto m a b -> Auto m (Blip a) (Blip b) perBlip = dimap to from . right where to = blip (Left ()) Right from = either (const NoBlip) Blip