Yampa 0.9.5 → 0.9.6
raw patch · 4 files changed
+395/−110 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ FRP.Yampa: pause :: b -> SF a Bool -> SF a b -> SF a b
Files
- CHANGELOG +11/−1
- Yampa.cabal +1/−1
- src/FRP/Yampa.hs +380/−105
- src/FRP/Yampa/Event.hs +3/−3
CHANGELOG view
@@ -1,4 +1,14 @@-2014-04-07 Ivan Perez <ivan.perez@keera.es>+2014-08-29 Ivan Perez <ivan.perez@keera.co.uk>++ * Yampa.cabal: version bump (0.9.6)+ * src/: Adds a substantial amount of documentation.+ * src/FRP/Yampa.hs: Adds a new pause combinator.++2014-06-04 Ivan Perez <ivan.perez@keera.co.uk>++ * Adds project to hudson-backed continuous integration server.++2014-04-26 Ivan Perez <ivan.perez@keera.es> * Yampa.cabal: version bump (0.9.5) * Adds CHANGELOG to cabal file
Yampa.cabal view
@@ -1,5 +1,5 @@ name: Yampa-version: 0.9.5+version: 0.9.6 cabal-version: >= 1.6 license: BSD3 license-file: LICENSE
src/FRP/Yampa.hs view
@@ -5,12 +5,57 @@ -- Copyright : (c) Antony Courtney and Henrik Nilsson, Yale University, 2003 -- License : BSD-style (see the LICENSE file in the distribution) ----- Maintainer : nilsson@cs.yale.edu+-- Maintainer : ivan.perez@keera.co.uk -- Stability : provisional -- Portability : non-portable (GHC extensions)+-- ----- New version using GADTs.+-- Domain-specific language embedded in Haskell for programming hybrid (mixed+-- discrete-time and continuous-time) systems. Yampa is based on the concepts+-- of Functional Reactive Programming (FRP) and is structured using arrow+-- combinators. --+-- You can find examples, tutorials and documentation on Yampa here:+--+-- <www.haskell.org/haskellwiki/Yampa>+--+-- Structuring a hybrid system in Yampa is done based on two main concepts:+--+-- * Signal Functions: 'SF'. Yampa is based on the concept of Signal Functions,+-- which are functions from a typed input signal to a typed output signal.+-- Conceptually, signals are functions from Time to Value, where time are the+-- real numbers and, computationally, a very dense approximation (Double) is+-- used.+--+-- * Events: 'Event'. Values that may or may not occur (and would probably+-- occur rarely). It is often used for incoming network messages, mouse+-- clicks, etc. Events are used as values carried by signals.+--+-- A complete Yampa system is defined as one Signal Function from some+-- type @a@ to a type @b@. The execution of this signal transformer+-- with specific input can be accomplished by means of two functions:+-- 'reactimate' (which needs an initialization action,+-- an input sensing action and an actuation/consumer action and executes+-- until explicitly stopped), and 'react' (which executes only one cycle).+-- +-- Apart from using normal functions and arrow syntax to define 'SF's, you+-- can also use several combinators. See [<#g:4>] for basic signals combinators,+-- [<#g:11>] for ways of switching from one signal transformation to another,+-- and [<#g:16>] for ways of transforming Event-carrying signals into continuous+-- signals, [<#g:19>] for ways of delaying signals, and [<#g:21>] for ways to+-- feed a signal back to the same signal transformer.+--+-- Ways to define Event-carrying signals are given in [<#g:7>], and+-- "FRP.Yampa.Event" defines events and event-manipulation functions.+--+-- Finally, see [<#g:26>] for sources of randomness (useful in games).+--+-- CHANGELOG:+--+-- * Adds (most) documentation.+--+-- * New version using GADTs.+-- -- ToDo: -- -- * Specialize def. of repeatedly. Could have an impact on invaders.@@ -102,13 +147,9 @@ RandomGen(..), Random(..), --- Reverse function composition and arrow plumbing aids- ( # ), -- :: (a -> b) -> (b -> c) -> (a -> c), infixl 9- dup, -- :: a -> (a,a)- swap, -- :: (a,b) -> (b,a)---- Main types+ -- * Basic definitions Time, -- [s] Both for time w.r.t. some reference and intervals.+ DTime, -- [s] Sampling interval, always > 0. SF, -- Signal Function. Event(..), -- Events; conceptually similar to Maybe (but abstract). @@ -132,8 +173,8 @@ -- (==) :: Event a -> Event a -> Bool -- (<=) :: Event a -> Event a -> Bool --- For optimization- arrPrim, arrEPrim,+ -- ** Lifting+ arrPrim, arrEPrim, -- For optimization -- * Signal functions @@ -248,9 +289,6 @@ trackAndHold, -- :: a -> SF (Maybe a) a -- ** Accumulators- old_accum, -- :: a -> SF (Event (a -> a)) (Event a)- old_accumBy, -- :: (b -> a -> b) -> b -> SF (Event a) (Event b)- old_accumFilter, -- :: (c -> a -> (c, Maybe b)) -> c accum, -- :: a -> SF (Event (a -> a)) (Event a) accumHold, -- :: a -> SF (Event (a -> a)) a dAccumHold, -- :: a -> SF (Event (a -> a)) a@@ -259,16 +297,22 @@ dAccumHoldBy, -- :: (b -> a -> b) -> b -> SF (Event a) b accumFilter, -- :: (c -> a -> (c, Maybe b)) -> c -- -> SF (Event a) (Event b)+ old_accum, -- :: a -> SF (Event (a -> a)) (Event a)+ old_accumBy, -- :: (b -> a -> b) -> b -> SF (Event a) (Event b)+ old_accumFilter, -- :: (c -> a -> (c, Maybe b)) -> c -- * Delays -- ** Basic delays- old_pre, old_iPre, pre, -- :: SF a a iPre, -- :: a -> SF a a+ old_pre, old_iPre, -- ** Timed delays delay, -- :: Time -> a -> SF a a +-- ** Variable delay+ pause, -- :: b -> SF a b -> SF a Bool -> SF a b+ -- * State keeping combinators -- ** Loops with guaranteed well-defined feedback@@ -281,6 +325,9 @@ derivative, -- :: VectorSpace a s => SF a a -- Crude! imIntegral, -- :: VectorSpace a s => a -> SF a a + -- Temporarily hidden, but will eventually be made public.+ -- iterFrom, -- :: (a -> a -> DTime -> b -> b) -> b -> SF a b+ -- * Noise (random signal) sources and stochastic event sources noise, -- :: noise :: (RandomGen g, Random b) => -- g -> SF a b@@ -307,13 +354,19 @@ -- * Embedding -- (tentative: will be revisited)- DTime, -- [s] Sampling interval, always > 0. embed, -- :: SF a b -> (a, [(DTime, Maybe a)]) -> [b] embedSynch, -- :: SF a b -> (a, [(DTime, Maybe a)]) -> SF Double b deltaEncode, -- :: Eq a => DTime -> [a] -> (a, [(DTime, Maybe a)])- deltaEncodeBy -- :: (a -> a -> Bool) -> DTime -> [a]+ deltaEncodeBy, -- :: (a -> a -> Bool) -> DTime -> [a] -- -> (a, [(DTime, Maybe a)]) + -- * Auxiliary definitions+ -- Reverse function composition and arrow plumbing aids+ ( # ), -- :: (a -> b) -> (b -> c) -> (a -> c), infixl 9+ dup, -- :: a -> (a,a)+ swap, -- :: (a,b) -> (b,a)++ ) where import Control.Monad (unless)@@ -351,13 +404,15 @@ -- switch would need to remember the record, since it is the only place -- where signal functions get started. So it wouldn't cost all that much. + -- | Time is used both for time intervals (duration), and time w.r.t. some--- agreed reference point in time. Conceptually, Time = R, i.e. time can be 0--- or even negative.+-- agreed reference point in time.++-- Conceptually, Time = R, i.e. time can be 0 -- or even negative. type Time = Double -- [s] --- DTime is the time type for lengths of sample intervals. Conceptually,+-- | DTime is the time type for lengths of sample intervals. Conceptually, -- DTime = R+ = { x in R | x > 0 }. Don't assume Time and DTime have the -- same representation. type DTime = Double -- [s]@@ -480,7 +535,6 @@ sfNever :: SF' a (Event b) sfNever = sfConst NoEvent - -- Assumption: fne = f NoEvent sfArrE :: (Event a -> b) -> b -> SF' (Event a) b sfArrE f fne = sf@@ -680,13 +734,15 @@ -- Lifting.++-- | Lifts a pure function into a signal function (applied pointwise). {-# NOINLINE arrPrim #-} arrPrim :: (a -> b) -> SF a b arrPrim f = SF {sfTF = \a -> (sfArrG f, f a)} -+-- | Lifts a pure function into a signal function applied to events+-- (applied pointwise). {-# RULES "arrPrim/arrEPrim" arrPrim = arrEPrim #-}- arrEPrim :: (Event a -> b) -> SF (Event a) b arrEPrim f = SF {sfTF = \a -> (sfArrE f (f NoEvent), f a)} @@ -1679,52 +1735,68 @@ -- Basic signal functions ------------------------------------------------------------------------------ --- Identity: identity = arr id+-- | Identity: identity = arr id+-- +-- Using 'identity' is preferred over lifting id, since the arrow combinators+-- know how to optimise certain networks based on the transformations being+-- applied. identity :: SF a a identity = SF {sfTF = \a -> (sfId, a)} ---- Identity: constant b = arr (const b)+-- | Identity: constant b = arr (const b)+-- +-- Using 'constant' is preferred over lifting const, since the arrow combinators+-- know how to optimise certain networks based on the transformations being+-- applied. constant :: b -> SF a b constant b = SF {sfTF = \_ -> (sfConst b, b)} ---- Outputs the time passed since the signal function instance was started.+-- | Outputs the time passed since the signal function instance was started. localTime :: SF a Time localTime = constant 1.0 >>> integral ---- Alternative name for localTime.+-- | Alternative name for localTime. time :: SF a Time time = localTime - ------------------------------------------------------------------------------ -- Initialization ------------------------------------------------------------------------------ --- Initialization operator (cf. Lustre/Lucid Synchrone).+-- | Initialization operator (cf. Lustre/Lucid Synchrone).+--+-- The output at time zero is the first argument, and from+-- that point on it behaves like the signal function passed as+-- second argument. (-->) :: b -> SF a b -> SF a b b0 --> (SF {sfTF = tf10}) = SF {sfTF = \a0 -> (fst (tf10 a0), b0)} ---- Input initialization operator.+-- | Input initialization operator.+--+-- The input at time zero is the first argument, and from+-- that point on it behaves like the signal function passed as+-- second argument. (>--) :: a -> SF a b -> SF a b a0 >-- (SF {sfTF = tf10}) = SF {sfTF = \_ -> tf10 a0} --- Transform initial output value.+-- | Transform initial output value.+--+-- Applies a transformation 'f' only to the first output value at+-- time zero. (-=>) :: (b -> b) -> SF a b -> SF a b f -=> (SF {sfTF = tf10}) = SF {sfTF = \a0 -> let (sf1, b0) = tf10 a0 in (sf1, f b0)} --- Transform initial input value.+-- | Transform initial input value.+--+-- Applies a transformation 'f' only to the first input value at+-- time zero. (>=-) :: (a -> a) -> SF a b -> SF a b f >=- (SF {sfTF = tf10}) = SF {sfTF = \a0 -> tf10 (f a0)} ---- Override initial value of input signal.+-- | Override initial value of input signal. initially :: a -> SF a a initially = (--> identity) @@ -1838,11 +1910,17 @@ t' = t + dt -} --- Or keep old def. for efficiency reasons?+-- | Event source with consecutive occurrences at the given intervals.+-- Should more than one event be scheduled to occur in any sampling interval,+-- only the first will in fact occur to avoid an event backlog.+ -- After all, after, repeatedly etc. are defined in terms of afterEach. afterEach :: [(Time,b)] -> SF a (Event b) afterEach qxs = afterEachCat qxs >>> arr (fmap head) +-- | Event source with consecutive occurrences at the given intervals.+-- Should more than one event be scheduled to occur in any sampling interval,+-- the output list will contain all events produced during that interval. -- Guaranteed not to miss any events. afterEachCat :: [(Time,b)] -> SF a (Event [b])@@ -1870,7 +1948,8 @@ where t' = t + dt --- Delay for events. (Consider it a triggered after, hence "basic".)+-- | Delay for events. (Consider it a triggered after, hence /basic/.)+ -- Can be implemented fairly cheaply as long as the events are sparse. -- It is a question of rescheduling events for later. Not unlike "afterEach". --@@ -1946,7 +2025,8 @@ (x' : rxs) -} --- This version is not strict in the input event.+-- | Delay an event by a given delta and catenate events that occur so closely+-- so as to be /inseparable/. delayEventCat :: Time -> SF (Event a) (Event [a]) delayEventCat q | q < 0 = usrErr "AFRP" "delayEventCat" "Negative delay." | q == 0 = arr (fmap (:[]))@@ -2029,7 +2109,9 @@ edge :: SF Bool (Event ()) edge = iEdge True -+-- | A rising edge detector that can be initialized as up ('True', meaning+-- that events occurring at time 0 will not be detected) or down+-- ('False', meaning that events ocurring at time 0 will be detected). iEdge :: Bool -> SF Bool (Event ()) -- iEdge i = edgeBy (isBoolRaisingEdge ()) i iEdge b = sscanPrim f (if b then 2 else 0) NoEvent@@ -2043,7 +2125,7 @@ f 2 True = Nothing f _ _ = undefined --- Like edge, but parameterized on the tag value.+-- | Like 'edge', but parameterized on the tag value. edgeTag :: a -> SF Bool (Event a) -- edgeTag a = edgeBy (isBoolRaisingEdge a) True edgeTag a = edge >>> arr (`tag` a)@@ -2057,6 +2139,9 @@ -- isBoolRaisingEdge _ True False = Nothing +-- | Edge detector particularized for detecting transtitions+-- on a 'Maybe' signal from 'Nothing' to 'Just'.+ -- !!! 2005-07-09: To be done or eliminated -- !!! Maybe could be kept as is, but could be easy to implement directly -- !!! in terms of sscan?@@ -2069,7 +2154,7 @@ isJustEdge (Just _) Nothing = Nothing --- Edge detector parameterized on the edge detection function and initial+-- | Edge detector parameterized on the edge detection function and initial -- state, i.e., the previous input sample. The first argument to the -- edge detection function is the previous sample, the second the current one. @@ -2090,17 +2175,17 @@ -- Stateful event suppression ------------------------------------------------------------------------------ --- Suppression of initial (at local time 0) event.+-- | Suppression of initial (at local time 0) event. notYet :: SF (Event a) (Event a) notYet = initially NoEvent --- Suppress all but first event.+-- | Suppress all but the first event. once :: SF (Event a) (Event a) once = takeEvents 1 --- Suppress all but first n events.+-- | Suppress all but the first n events. takeEvents :: Int -> SF (Event a) (Event a) takeEvents n | n <= 0 = never takeEvents n = dSwitch (arr dup) (const (NoEvent >-- takeEvents (n - 1)))@@ -2117,7 +2202,8 @@ -} --- Suppress first n events.+-- | Suppress first n events.+ -- Here dSwitch or switch does not really matter. dropEvents :: Int -> SF (Event a) (Event a) dropEvents n | n <= 0 = identity@@ -2179,7 +2265,26 @@ (_, Event c) -> sfTF (k c) a -} --- Basic switch.+-- | Basic switch.+-- +-- By default, the first signal function is applied.+--+-- Whenever the second value in the pair actually is an event,+-- the value carried by the event is used to obtain a new signal+-- function to be applied *at that time and at future times*.+-- +-- Until that happens, the first value in the pair is produced+-- in the output signal.+--+-- Important note: at the time of switching, the second+-- signal function is applied immediately. If that second+-- SF can also switch at time zero, then a double (nested)+-- switch might take place. If the second SF refers to the+-- first one, the switch might take place infinitely many+-- times and never be resolved.+--+-- Remember: The continuation is evaluated strictly at the time+-- of switching! switch :: SF a (b, Event c) -> (c -> SF a b) -> SF a b switch (SF {sfTF = tf10}) k = SF {sfTF = tf0} where@@ -2234,8 +2339,31 @@ (_, Event c) -> sfTF (k c) a --- Switch with delayed observation.--- Or "decoupled switch"?+-- | Switch with delayed observation.+-- +-- By default, the first signal function is applied.+--+-- Whenever the second value in the pair actually is an event,+-- the value carried by the event is used to obtain a new signal+-- function to be applied *at future times*.+-- +-- Until that happens, the first value in the pair is produced+-- in the output signal.+--+-- Important note: at the time of switching, the second+-- signal function is used immediately, but the current+-- input is fed by it (even though the actual output signal+-- value at time 0 is discarded). +-- +-- If that second SF can also switch at time zero, then a+-- double (nested) -- switch might take place. If the second SF refers to the+-- first one, the switch might take place infinitely many times and never be+-- resolved.+--+-- Remember: The continuation is evaluated strictly at the time+-- of switching!++-- Alternative name: "decoupled switch"? -- (The SFId optimization is highly unlikley to be of much use, but it -- does raise an interesting typing issue.) dSwitch :: SF a (b, Event c) -> (c -> SF a b) -> SF a b@@ -2302,7 +2430,11 @@ b) --- Recurring switch.+-- | Recurring switch.+-- +-- See <http://www.haskell.org/haskellwiki/Yampa#Switches> for more+-- information on how this switch works.+ -- !!! Suboptimal. Overall, the constructor is invarying since rSwitch is -- !!! being invoked recursively on a switch. In fact, we don't even care -- !!! whether the subordinate signal function is invarying or not.@@ -2322,7 +2454,10 @@ -} --- Recurring switch with delayed observation.+-- | Recurring switch with delayed observation.+-- +-- See <http://www.haskell.org/haskellwiki/Yampa#Switches> for more+-- information on how this switch works. drSwitch :: SF a b -> SF (a, Event (SF a b)) b drSwitch sf = dSwitch (first sf) ((noEventSnd >=-) . drSwitch) @@ -2335,7 +2470,11 @@ -} --- "Call-with-current-continuation" switch.+-- | "Call-with-current-continuation" switch.+-- +-- See <http://www.haskell.org/haskellwiki/Yampa#Switches> for more+-- information on how this switch works.+ -- !!! Has not been optimized properly. -- !!! Nor has opts been tested! -- !!! Don't forget Inv opts!@@ -2450,7 +2589,11 @@ Event c -> sfTF (k (arr f1) c) a --- kSwitch with delayed observation.+-- | 'kSwitch' with delayed observation.+-- +-- See <http://www.haskell.org/haskellwiki/Yampa#Switches> for more+-- information on how this switch works.+ -- !!! Has not been optimized properly. Should be like kSwitch. dkSwitch :: SF a b -> SF (a,b) (Event c) -> (SF a b -> c -> SF a b) -> SF a b dkSwitch sf10@(SF {sfTF = tf10}) (SF {sfTF = tfe0}) k = SF {sfTF = tf0}@@ -2477,6 +2620,8 @@ -- Parallel composition and switching over collections with broadcasting ------------------------------------------------------------------------------ +-- | Tuple a value up with every element of a collection of signal+-- functions. broadcast :: Functor col => a -> col sf -> col (a, sf) broadcast a sfs = fmap (\sf -> (a, sf)) sfs @@ -2506,30 +2651,45 @@ -- !!! = arr1 &&& (arr23CcpXcpX) >>> arrX -- !!! = arr123CcpXcpXcpX --- Spatial parallel composition of a signal function collection.+-- | Spatial parallel composition of a signal function collection.+-- Given a collection of signal functions, it returns a signal+-- function that 'broadcast's its input signal to every element+-- of the collection, to return a signal carrying a collection+-- of outputs. See 'par'.+--+-- For more information on how parallel composition works, check+-- <http://haskell.cs.yale.edu/wp-content/uploads/2011/01/yampa-arcade.pdf> parB :: Functor col => col (SF a b) -> SF a (col b) parB = par broadcast ---- Parallel switch (dynamic collection of signal functions spatially composed--- in parallel).+-- | Parallel switch (dynamic collection of signal functions spatially composed+-- in parallel). See 'pSwitch'.+--+-- For more information on how parallel composition works, check+-- <http://haskell.cs.yale.edu/wp-content/uploads/2011/01/yampa-arcade.pdf> pSwitchB :: Functor col => col (SF a b) -> SF (a,col b) (Event c) -> (col (SF a b)->c-> SF a (col b)) -> SF a (col b) pSwitchB = pSwitch broadcast -+-- | Delayed parallel switch with broadcasting (dynamic collection of+-- signal functions spatially composed in parallel). See 'dpSwitch'.+-- +-- For more information on how parallel composition works, check+-- <http://haskell.cs.yale.edu/wp-content/uploads/2011/01/yampa-arcade.pdf> dpSwitchB :: Functor col => col (SF a b) -> SF (a,col b) (Event c) -> (col (SF a b)->c->SF a (col b)) -> SF a (col b) dpSwitchB = dpSwitch broadcast -+-- For more information on how parallel composition works, check+-- <http://haskell.cs.yale.edu/wp-content/uploads/2011/01/yampa-arcade.pdf> rpSwitchB :: Functor col => col (SF a b) -> SF (a, Event (col (SF a b) -> col (SF a b))) (col b) rpSwitchB = rpSwitch broadcast -+-- For more information on how parallel composition works, check+-- <http://haskell.cs.yale.edu/wp-content/uploads/2011/01/yampa-arcade.pdf> drpSwitchB :: Functor col => col (SF a b) -> SF (a, Event (col (SF a b) -> col (SF a b))) (col b) drpSwitchB = drpSwitch broadcast@@ -2539,17 +2699,15 @@ -- Parallel composition and switching over collections with general routing ------------------------------------------------------------------------------ --- Spatial parallel composition of a signal function collection parameterized+-- | Spatial parallel composition of a signal function collection parameterized -- on the routing function.--- rf ......... Routing function: determines the input to each signal function--- in the collection. IMPORTANT! The routing function MUST--- preserve the structure of the signal function collection.--- sfs0 ....... Signal function collection.--- Returns the spatial parallel composition of the supplied signal functions.-+-- par :: Functor col =>- (forall sf . (a -> col sf -> col (b, sf)))- -> col (SF b c)+ (forall sf . (a -> col sf -> col (b, sf))) -- ^ Determines the input to each signal function+ -- in the collection. IMPORTANT! The routing function MUST+ -- preserve the structure of the signal function collection.++ -> col (SF b c) -- ^ Signal function collection. -> SF a (col c) par rf sfs0 = SF {sfTF = tf0} where@@ -2578,13 +2736,15 @@ (parAux rf sfs', cs) --- Parallel switch parameterized on the routing function. This is the most+-- | Parallel switch parameterized on the routing function. This is the most -- general switch from which all other (non-delayed) switches in principle -- can be derived. The signal function collection is spatially composed in -- parallel and run until the event signal function has an occurrence. Once -- the switching event occurs, all signal function are "frozen" and their -- continuations are passed to the continuation function, along with the -- event value.+--+ -- rf ......... Routing function: determines the input to each signal function -- in the collection. IMPORTANT! The routing function has an -- obligation to preserve the structure of the signal function@@ -2595,12 +2755,15 @@ -- Returns the resulting signal function. -- -- !!! Could be optimized on the event source being SFArr, SFArrE, SFArrEE----pSwitch :: Functor col =>- (forall sf . (a -> col sf -> col (b, sf)))- -> col (SF b c)- -> SF (a, col c) (Event d)- -> (col (SF b c) -> d -> SF a (col c))+pSwitch :: Functor col+ => (forall sf . (a -> col sf -> col (b, sf))) -- ^ Routing function: determines the input to each signal function+ -- in the collection. IMPORTANT! The routing function has an+ -- obligation to preserve the structure of the signal function+ -- collection.++ -> col (SF b c) -- ^ Signal function collection.+ -> SF (a, col c) (Event d) -- ^ Signal function generating the switching event.+ -> (col (SF b c) -> d -> SF a (col c)) -- ^ Continuation to be invoked once event occurs. -> SF a (col c) pSwitch rf sfs0 sfe0 k = SF {sfTF = tf0} where@@ -2628,16 +2791,36 @@ (_, Event d) -> sfTF (k (freezeCol sfs dt) d) a --- Parallel switch with delayed observation parameterized on the routing+-- | Parallel switch with delayed observation parameterized on the routing -- function. --+-- The collection argument to the function invoked on the+-- switching event is of particular interest: it captures the+-- continuations of the signal functions running in the collection+-- maintained by 'dpSwitch' at the time of the switching event,+-- thus making it possible to preserve their state across a switch.+-- Since the continuations are plain, ordinary signal functions,+-- they can be resumed, discarded, stored, or combined with+-- other signal functions.+ -- !!! Could be optimized on the event source being SFArr, SFArrE, SFArrEE. -- dpSwitch :: Functor col =>- (forall sf . (a -> col sf -> col (b, sf)))- -> col (SF b c)- -> SF (a, col c) (Event d)- -> (col (SF b c) -> d -> SF a (col c))+ (forall sf . (a -> col sf -> col (b, sf))) -- ^ Routing function. Its purpose is+ -- to pair up each running signal function in the collection+ -- maintained by 'dpSwitch' with the input it is going to see+ -- at each point in time. All the routing function can do is specify+ -- how the input is distributed.+ -> col (SF b c) -- ^ Initial collection of signal functions.+ -> SF (a, col c) (Event d) -- ^ Signal function that observes the external+ -- input signal and the output signals from the collection in order+ -- to produce a switching event.+ -> (col (SF b c) -> d -> SF a (col c)) -- ^ The fourth argument is a function that is invoked when the+ -- switching event occurs, yielding a new signal function to switch+ -- into based on the collection of signal functions previously+ -- running and the value carried by the switching event. This+ -- allows the collection to be updated and then switched back+ -- in, typically by employing 'dpSwitch' again. -> SF a (col c) dpSwitch rf sfs0 sfe0 k = SF {sfTF = tf0} where@@ -2707,18 +2890,19 @@ drpSwitch' sfs = dpSwitch (rf . fst) sfs (NoEvent-->arr (snd . fst)) k -} - ------------------------------------------------------------------------------ -- Wave-form generation ------------------------------------------------------------------------------ --- Zero-order hold.+-- | Zero-order hold.+ -- !!! Should be redone using SFSScan? -- !!! Otherwise, we are missing an invarying case. old_hold :: a -> SF (Event a) a old_hold a_init = switch (constant a_init &&& identity) ((NoEvent >--) . old_hold) +-- | Zero-order hold. hold :: a -> SF (Event a) a hold a_init = epPrim f () a_init where@@ -2740,7 +2924,9 @@ -- !!! and ep + sscan = sscan, then things might work, and -- !!! it might be possible to define dHold simply as hold >>> iPre -- !!! without any performance penalty. --- Zero-order hold with delay.++-- | Zero-order hold with delay.+-- -- Identity: dHold a0 = hold a0 >>> iPre a0). dHold :: a -> SF (Event a) a dHold a0 = hold a0 >>> iPre a0@@ -2751,7 +2937,8 @@ f a' a = (a, a', a) -} --- Tracks input signal when available, holds last value when disappears.+-- | Tracks input signal when available, holds last value when disappears.+-- -- !!! DANGER!!! Event used inside arr! Probably OK because arr will not be -- !!! optimized to arrE. But still. Maybe rewrite this using, say, scan? -- !!! or switch? Switching (in hold) for every input sample does not@@ -2764,24 +2951,37 @@ -- Accumulators ------------------------------------------------------------------------------ +-- | See 'accum'. old_accum :: a -> SF (Event (a -> a)) (Event a) old_accum = accumBy (flip ($)) +-- | Given an initial value in an accumulator,+-- it returns a signal function that processes+-- an event carrying transformation functions.+-- Every time an 'Event' is received, the function+-- inside it is applied to the accumulator,+-- whose new value is outputted in an 'Event'.+-- accum :: a -> SF (Event (a -> a)) (Event a) accum a_init = epPrim f a_init NoEvent where- f a g = (a', Event a', NoEvent)+ f a g = (a', Event a', NoEvent) -- Accumulator, output if Event, output if no event where a' = g a +-- | Zero-order hold accumulator (always produces the last outputted value+-- until an event arrives). accumHold :: a -> SF (Event (a -> a)) a accumHold a_init = epPrim f a_init a_init where- f a g = (a', a', a')+ f a g = (a', a', a') -- Accumulator, output if Event, output if no event where a' = g a +-- | Zero-order hold accumulator with delayed initialization (always produces+-- the last outputted value until an event arrives, but the very initial output +-- is always the given accumulator). dAccumHold :: a -> SF (Event (a -> a)) a dAccumHold a_init = accumHold a_init >>> iPre a_init {-@@ -2797,11 +2997,13 @@ -} +-- | See 'accumBy'. old_accumBy :: (b -> a -> b) -> b -> SF (Event a) (Event b) old_accumBy f b_init = switch (never &&& identity) $ \a -> abAux (f b_init a) where abAux b = switch (now b &&& notYet) $ \a -> abAux (f b a) +-- | Accumulator parameterized by the accumulation function. accumBy :: (b -> a -> b) -> b -> SF (Event a) (Event b) accumBy g b_init = epPrim f b_init NoEvent where@@ -2809,6 +3011,7 @@ where b' = g b a +-- | Zero-order hold accumulator parameterized by the accumulation function. accumHoldBy :: (b -> a -> b) -> b -> SF (Event a) b accumHoldBy g b_init = epPrim f b_init b_init where@@ -2820,6 +3023,9 @@ -- !!! on the input at every time step. -- !!! Add a test case to check for this! +-- | Zero-order hold accumulator parameterized by the accumulation function+-- with delayed initialization (initial output sample is always the+-- given accumulator). dAccumHoldBy :: (b -> a -> b) -> b -> SF (Event a) b dAccumHoldBy f a_init = accumHoldBy f a_init >>> iPre a_init {-@@ -2879,13 +3085,17 @@ (c', Just b) -> (afAux c', Event b) -} -+-- | See 'accumFilter'. old_accumFilter :: (c -> a -> (c, Maybe b)) -> c -> SF (Event a) (Event b) old_accumFilter f c_init = switch (never &&& identity) $ \a -> afAux (f c_init a) where afAux (c, Nothing) = switch (never &&& notYet) $ \a -> afAux (f c a) afAux (c, Just b) = switch (now b &&& notYet) $ \a -> afAux (f c a) +-- | Accumulator parameterized by the accumulator function with filtering,+-- possibly discarding some of the input events based on whether the second+-- component of the result of applying the accumulation function is+-- 'Nothing' or 'Just' x for some x. accumFilter :: (c -> a -> (c, Maybe b)) -> c -> SF (Event a) (Event b) accumFilter g c_init = epPrim f c_init NoEvent where@@ -2898,7 +3108,8 @@ -- Delays ------------------------------------------------------------------------------ --- Uninitialized delay operator.+-- | Uninitialized delay operator (old implementation).+ -- !!! The seq helps in the dynamic delay line example. But is it a good -- !!! idea in general? Are there other accumulators which should be seq'ed -- !!! as well? E.g. accum? Switch? Anywhere else? What's the underlying@@ -2913,12 +3124,14 @@ where tf _ a = {- a_prev `seq` -} (preAux a, a_prev) --- Initialized delay operator.+-- | Initialized delay operator (old implementation). old_iPre :: a -> SF a a old_iPre = (--> old_pre) +-- | Uninitialized delay operator.+ -- !!! Redefined using SFSScan -- !!! About 20% slower than old_pre on its own. pre :: SF a a@@ -2928,7 +3141,7 @@ uninit = usrErr "AFRP" "pre" "Uninitialized pre operator." --- Initialized delay operator.+-- | Initialized delay operator. iPre :: a -> SF a a iPre = (--> pre) @@ -2937,6 +3150,8 @@ -- Timed delays ------------------------------------------------------------------------------ +-- | Delay a signal by a fixed time 't', using the second parameter+-- to fill in the initial 't' seconds. -- Invariants: -- t_diff measure the time since the latest output sample ideally@@ -2986,10 +3201,55 @@ ------------------------------------------------------------------------------+-- Variable pause in signal+------------------------------------------------------------------------------++-- | Given a value in an accumulator (b), a predicate signal function (sfC), +-- and a second signal function (sf), pause will produce the accumulator b+-- if sfC input is True, and will transform the signal using sf otherwise.+-- It acts as a pause with an accumulator for the moments when the+-- transformation is paused.+pause :: b -> SF a Bool -> SF a b -> SF a b+pause b_init (SF { sfTF = tfP}) (SF {sfTF = tf10}) = SF {sfTF = tf0}+ where+ -- Initial transformation (no time delta):+ -- If the condition is True, return the accumulator b_init)+ -- Otherwise transform the input normally and recurse.+ tf0 a0 = case tfP a0 of+ (c, True) -> (pauseInit b_init tf10 c, b_init)+ (c, False) -> let (k, b0) = tf10 a0+ in (pause' b0 k c, b0)++ -- Similar deal, but with a time delta+ pauseInit :: b -> (a -> Transition a b) -> SF' a Bool -> SF' a b+ pauseInit b_init' tf10' c = SF' tf0'+ where tf0' dt a =+ case (sfTF' c) dt a of+ (c', True) -> (pauseInit b_init' tf10' c', b_init')+ (c', False) -> let (k, b0) = tf10' a+ in (pause' b0 k c', b0)++ -- Very same deal (almost alpha-renameable)+ pause' :: b -> SF' a b -> SF' a Bool -> SF' a b+ pause' b_init' tf10' tfP' = SF' tf0'+ where tf0' dt a = + case (sfTF' tfP') dt a of+ (tfP'', True) -> (pause' b_init' tf10' tfP'', b_init')+ (tfP'', False) -> let (tf10'', b0') = (sfTF' tf10') dt a+ in (pause' b0' tf10'' tfP'', b0')++-- if_then_else :: SF a Bool -> SF a b -> SF a b -> SF a b+-- if_then_else condSF sfThen sfElse = proc (i) -> do+-- cond <- condSF -< i+-- ok <- sfThen -< i+-- notOk <- sfElse -< i+-- returnA -< if cond then ok else notOk++------------------------------------------------------------------------------ -- Integration and differentiation ------------------------------------------------------------------------------ --- Integration using the rectangle rule.+-- | Integration using the rectangle rule. {-# INLINE integral #-} integral :: VectorSpace a s => SF a a integral = SF {sfTF = tf0}@@ -3014,8 +3274,9 @@ -- iterAux b a = (SF' (\ dt a' -> iterAux (f a a' dt b) a') True, b) iterAux b a = (SF' (\ dt a' -> iterAux (f a a' dt b) a'), b) ---- This is extremely crude. Use at your own risk.+-- | A very crude version of a derivative. It simply divides the+-- value difference by the time difference. As such, it is very+-- crude. Use at your own risk. derivative :: VectorSpace a s => SF a a derivative = SF {sfTF = tf0} where@@ -3030,11 +3291,13 @@ -- Loops with guaranteed well-defined feedback ------------------------------------------------------------------------------ +-- | Loop with an initial value for the signal being fed back. loopPre :: c -> SF (a,c) (b,c) -> SF a b loopPre c_init sf = loop (second (iPre c_init) >>> sf) --+-- | Loop by integrating the second value in the pair and feeding the+-- result back. Because the integral at time 0 is zero, this is always+-- well defined. loopIntegral :: VectorSpace c s => SF (a,c) (b,c) -> SF a b loopIntegral sf = loop (second integral >>> sf) @@ -3043,13 +3306,13 @@ -- Noise (i.e. random signal generators) and stochastic processes ------------------------------------------------------------------------------ --- Noise (random signal) with default range for type in question;+-- | Noise (random signal) with default range for type in question; -- based on "randoms". noise :: (RandomGen g, Random b) => g -> SF a b noise g0 = streamToSF (randoms g0) --- Noise (random signal) with specified range; based on "randomRs".+-- | Noise (random signal) with specified range; based on "randomRs". noiseR :: (RandomGen g, Random b) => (b,b) -> g -> SF a b noiseR range g0 = streamToSF (randomRs range g0) @@ -3081,11 +3344,12 @@ -} --- Stochastic event source with events occurring on average once every t_avg+-- | Stochastic event source with events occurring on average once every t_avg -- seconds. However, no more than one event results from any one sampling -- interval in the case of relatively sparse sampling, thus avoiding an -- "event backlog" should sampling become more frequent at some later -- point in time.+ -- !!! Maybe it would better to give a frequency? But like this to make -- !!! consitent with "repeatedly". occasionally :: RandomGen g => g -> Time -> b -> SF a (Event b)@@ -3198,9 +3462,10 @@ rsB :: b } +-- | A reference to reactimate's state, maintained across samples. type ReactHandle a b = IORef (ReactState a b) --- initialize top-level reaction handle+-- | Initialize a top-level reaction handle. reactInit :: IO a -- init -> (ReactHandle a b -> Bool -> b -> IO Bool) -- actuate -> SF a b@@ -3214,7 +3479,7 @@ _ <- actuate r True b0 return r --- process a single input sample:+-- | Process a single input sample. react :: ReactHandle a b -> (DTime,Maybe a) -> IO Bool@@ -3233,7 +3498,7 @@ -- New embed interface. We will probably have to revisit this. To run an -- embedded signal function while retaining full control (e.g. start and--- stop at will), one would probably need a continuation based interface+-- stop at will), one would probably need a continuation-based interface -- (as well as a continuation based underlying implementation). -- -- E.g. here are interesting alternative (or maybe complementary)@@ -3255,6 +3520,12 @@ -- subSample :: DTime -> SF a b -> SF (Event a) (Event b) -- Time advanced by dt for each event, not synchronized with the outer clock. +-- | Given a signal function and a pair with an initial+-- input sample for the input signal, and a list of sampling+-- times, possibly with new input samples at those times,+-- it produces a list of output samples.+--+-- This is a simplified, purely-functional version of 'reactimate'. embed :: SF a b -> (a, [(DTime, Maybe a)]) -> [b] embed sf0 (a0, dtas) = b0 : loop a0 sf dtas where@@ -3268,10 +3539,10 @@ (sf', b) = (sfTF' sf) dt a --- Synchronous embedding. The embedded signal function is run on the supplied+-- | Synchronous embedding. The embedded signal function is run on the supplied -- input and time stream at a given (but variable) ratio >= 0 to the outer -- time flow. When the ratio is 0, the embedded signal function is paused.---+ -- What about running an embedded signal function at a fixed (guaranteed) -- sampling frequency? E.g. super sampling if the outer sampling is slower, -- subsampling otherwise. AS WELL as at a given ratio to the outer one.@@ -3314,11 +3585,15 @@ | t' <= tp = advance tp tbtbs advance _ _ = undefined +-- | Spaces a list of samples by a fixed time delta, avoiding+-- unnecessary samples when the input has not changed since+-- the last sample. deltaEncode :: Eq a => DTime -> [a] -> (a, [(DTime, Maybe a)]) deltaEncode _ [] = usrErr "AFRP" "deltaEncode" "Empty input list." deltaEncode dt aas@(_:_) = deltaEncodeBy (==) dt aas +-- | 'deltaEncode' parameterized by the equality test. deltaEncodeBy :: (a -> a -> Bool) -> DTime -> [a] -> (a, [(DTime, Maybe a)]) deltaEncodeBy _ _ [] = usrErr "AFRP" "deltaEncodeBy" "Empty input list." deltaEncodeBy eq dt (a0:as) = (a0, zip (repeat dt) (debAux a0 as))
src/FRP/Yampa/Event.hs view
@@ -256,17 +256,17 @@ mapMerge _ rf _ NoEvent (Event r) = Event (rf r) mapMerge _ _ lrf (Event l) (Event r) = Event (lrf l r) --- Merging of a list of events; foremost event has priority.+-- | Merge a list of events; foremost event has priority. mergeEvents :: [Event a] -> Event a mergeEvents = foldr lMerge NoEvent --- | Collects simultaneous event occurrences; no event if none.+-- | Collect simultaneous event occurrences; no event if none. catEvents :: [Event a] -> Event [a] catEvents eas = case [ a | Event a <- eas ] of [] -> NoEvent as -> Event as --- | Join (conjucntion) of two events. Only produces an event+-- | Join (conjunction) of two events. Only produces an event -- if both events exist. joinE :: Event a -> Event b -> Event (a,b) joinE NoEvent _ = NoEvent