Copyright | (c) Ivan Perez 2019-2022 (c) Ivan Perez and Manuel Baerenz 2016-2018 |
---|---|
License | BSD3 |
Maintainer | ivan.perez@keera.co.uk |
Safe Haskell | Safe-Inferred |
Language | Haskell2010 |
- Basic definitions
- Signal functions
- Simple, stateful signal processing
- Events
- Hybrid SF m combinators
- Stateful event suppression
- Pointwise functions on events
- Switching
- Parallel composition and switching
- Discrete to continuous-time signal functions
- State keeping combinators
- Integration and differentiation
- Noise (random signal) sources and stochastic event sources
- Execution/simulation
- Debugging / Step by step simulation
Implementation of Yampa using Monadic Stream Processing library.
Synopsis
- data Event a
- arrPrim :: Monad m => (a -> b) -> SF m a b
- arrEPrim :: Monad m => (Event a -> b) -> SF m (Event a) b
- localTime :: Monad m => SF m a Time
- time :: Monad m => SF m a Time
- sscan :: Monad m => (b -> a -> b) -> b -> SF m a b
- sscanPrim :: Monad m => (c -> a -> Maybe (c, b)) -> c -> b -> SF m a b
- never :: Monad m => SF m a (Event b)
- now :: Monad m => b -> SF m a (Event b)
- after :: Monad m => Time -> b -> SF m a (Event b)
- repeatedly :: Monad m => Time -> b -> SF m a (Event b)
- afterEach :: Monad m => [(Time, b)] -> SF m a (Event b)
- afterEachCat :: Monad m => [(Time, b)] -> SF m a (Event [b])
- mapEventS :: Monad m => MSF m a b -> MSF m (Event a) (Event b)
- eventToMaybe :: Event a -> Maybe a
- boolToEvent :: Bool -> Event ()
- edge :: Monad m => SF m Bool (Event ())
- iEdge :: Monad m => Bool -> SF m Bool (Event ())
- edgeTag :: Monad m => a -> SF m Bool (Event a)
- edgeJust :: Monad m => SF m (Maybe a) (Event a)
- edgeBy :: Monad m => (a -> a -> Maybe b) -> a -> SF m a (Event b)
- maybeToEvent :: Maybe a -> Event a
- edgeFrom :: Monad m => Bool -> SF m Bool (Event ())
- notYet :: Monad m => SF m (Event a) (Event a)
- once :: Monad m => SF m (Event a) (Event a)
- takeEvents :: Monad m => Int -> SF m (Event a) (Event a)
- dropEvents :: Monad m => Int -> SF m (Event a) (Event a)
- noEvent :: Event a
- noEventFst :: (Event a, b) -> (Event c, b)
- noEventSnd :: (a, Event b) -> (a, Event c)
- event :: a -> (b -> a) -> Event b -> a
- fromEvent :: Event a -> a
- isEvent :: Event a -> Bool
- isNoEvent :: Event a -> Bool
- tag :: Event a -> b -> Event b
- tagWith :: b -> Event a -> Event b
- attach :: Event a -> b -> Event (a, b)
- lMerge :: Event a -> Event a -> Event a
- rMerge :: Event a -> Event a -> Event a
- merge :: Event a -> Event a -> Event a
- mergeBy :: (a -> a -> a) -> Event a -> Event a -> Event a
- mapMerge :: (a -> c) -> (b -> c) -> (a -> b -> c) -> Event a -> Event b -> Event c
- mergeEvents :: [Event a] -> Event a
- catEvents :: [Event a] -> Event [a]
- joinE :: Event a -> Event b -> Event (a, b)
- splitE :: Event (a, b) -> (Event a, Event b)
- filterE :: (a -> Bool) -> Event a -> Event a
- mapFilterE :: (a -> Maybe b) -> Event a -> Event b
- gate :: Event a -> Bool -> Event a
- switch :: Monad m => SF m a (b, Event c) -> (c -> SF m a b) -> SF m a b
- dSwitch :: Monad m => SF m a (b, Event c) -> (c -> SF m a b) -> SF m a b
- parB :: Monad m => [SF m a b] -> SF m a [b]
- dpSwitchB :: (Functor m, Monad m, Traversable col) => col (SF m a b) -> SF m (a, col b) (Event c) -> (col (SF m a b) -> c -> SF m a (col b)) -> SF m a (col b)
- parC :: Monad m => SF m a b -> SF m [a] [b]
- hold :: Monad m => a -> SF m (Event a) a
- accumBy :: Monad m => (b -> a -> b) -> b -> SF m (Event a) (Event b)
- accumHoldBy :: Monad m => (b -> a -> b) -> b -> SF m (Event a) b
- loopPre :: Monad m => c -> SF m (a, c) (b, c) -> SF m a b
- integral :: (Monad m, Fractional s, VectorSpace a s) => SF m a a
- integralFrom :: (Monad m, Fractional s, VectorSpace a s) => a -> SF m a a
- derivative :: (Monad m, Fractional s, VectorSpace a s) => SF m a a
- derivativeFrom :: (Monad m, Fractional s, VectorSpace a s) => a -> SF m a a
- iterFrom :: Monad m => (a -> a -> DTime -> b -> b) -> b -> SF m a b
- occasionally :: MonadRandom m => Time -> b -> SF m a (Event b)
- reactimate :: Monad m => m a -> (Bool -> m (DTime, Maybe a)) -> (Bool -> b -> m Bool) -> SF Identity a b -> m ()
- evalAtZero :: SF Identity a b -> a -> (b, SF Identity a b)
- evalAt :: SF Identity a b -> DTime -> a -> (b, SF Identity a b)
- evalFuture :: SF Identity a b -> a -> DTime -> (b, SF Identity a b)
Basic definitions
A single possible event occurrence, that is, a value that may or may not occur. Events are used to represent values that are not produced continuously, such as mouse clicks (only produced when the mouse is clicked, as opposed to mouse positions, which are always defined).
Instances
MonadFail Event Source # | MonadFail instance |
Defined in FRP.BearRiver | |
Alternative Event Source # | Alternative instance |
Applicative Event Source # | The type |
Functor Event Source # | The type |
Monad Event Source # | The type |
Show a => Show (Event a) Source # | |
NFData a => NFData (Event a) Source # | NFData instance |
Defined in FRP.BearRiver | |
Eq a => Eq (Event a) Source # | |
Ord a => Ord (Event a) Source # | |
Lifting
arrPrim :: Monad m => (a -> b) -> SF m a b Source #
Lifts a pure function into a signal function (applied pointwise).
arrEPrim :: Monad m => (Event a -> b) -> SF m (Event a) b Source #
Lifts a pure function into a signal function applied to events (applied pointwise).
Signal functions
Basic signal functions
localTime :: Monad m => SF m a Time Source #
Outputs the time passed since the signal function instance was started.
Simple, stateful signal processing
sscan :: Monad m => (b -> a -> b) -> b -> SF m a b Source #
Applies a function point-wise, using the last output as next input. This creates a well-formed loop based on a pure, auxiliary function.
sscanPrim :: Monad m => (c -> a -> Maybe (c, b)) -> c -> b -> SF m a b Source #
Generic version of sscan
, in which the auxiliary function produces an
internal accumulator and an "held" output.
Applies a function point-wise, using the last known Just
output to form
the output, and next input accumulator. If the output is Nothing
, the last
known accumulators are used. This creates a well-formed loop based on a
pure, auxiliary function.
now :: Monad m => b -> SF m a (Event b) Source #
Event source with a single occurrence at time 0. The value of the event is given by the function argument.
:: Monad m | |
=> Time | The time q after which the event should be produced |
-> b | Value to produce at that time |
-> SF m a (Event b) |
Event source with a single occurrence at or as soon after (local) time q as possible.
repeatedly :: Monad m => Time -> b -> SF m a (Event b) Source #
Event source with repeated occurrences with interval q.
Note: If the interval is too short w.r.t. the sampling intervals, the result will be that events occur at every sample. However, no more than one event results from any sampling interval, thus avoiding an "event backlog" should sampling become more frequent at some later point in time.
afterEach :: Monad m => [(Time, b)] -> SF m a (Event b) Source #
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.
afterEachCat :: Monad m => [(Time, b)] -> SF m a (Event [b]) Source #
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.
Events
Relation to other types
eventToMaybe :: Event a -> Maybe a Source #
Convert an Event
into a Maybe
value.
Both types are isomorphic, where a value containing an event is mapped to a
Just
, and NoEvent
is mapped to Nothing
. There is, however, a semantic
difference: a signal carrying a Maybe may change constantly, but, for a
signal carrying an Event
, there should be a bounded frequency such that
sampling the signal faster does not render more event occurrences.
Hybrid SF m combinators
edge :: Monad m => SF m Bool (Event ()) Source #
A rising edge detector. Useful for things like detecting key presses. It is initialised as up, meaning that events occurring at time 0 will not be detected.
edgeTag :: Monad m => a -> SF m Bool (Event a) Source #
Like edge
, but parameterized on the tag value.
From Yampa
edgeBy :: Monad m => (a -> a -> Maybe b) -> a -> SF m a (Event b) Source #
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.
maybeToEvent :: Maybe a -> Event a Source #
Stateful event suppression
notYet :: Monad m => SF m (Event a) (Event a) Source #
Suppression of initial (at local time 0) event.
takeEvents :: Monad m => Int -> SF m (Event a) (Event a) Source #
Suppress all but the first n events.
Pointwise functions on events
Make the NoEvent constructor available. Useful e.g. for initialization, ((-->) & friends), and it's easily available anyway (e.g. mergeEvents []).
noEventFst :: (Event a, b) -> (Event c, b) Source #
Suppress any event in the first component of a pair.
noEventSnd :: (a, Event b) -> (a, Event c) Source #
Suppress any event in the second component of a pair.
tag :: Event a -> b -> Event b Source #
Tags an (occurring) event with a value ("replacing" the old value).
Applicative-based definition: tag = ($>)
tagWith :: b -> Event a -> Event b Source #
Tags an (occurring) event with a value ("replacing" the old value). Same
as tag
with the arguments swapped.
Applicative-based definition: tagWith = (<$)
attach :: Event a -> b -> Event (a, b) Source #
Attaches an extra value to the value of an occurring event.
lMerge :: Event a -> Event a -> Event a Source #
Left-biased event merge (always prefer left event, if present).
rMerge :: Event a -> Event a -> Event a Source #
Right-biased event merge (always prefer right event, if present).
merge :: Event a -> Event a -> Event a Source #
Unbiased event merge: simultaneous occurrence is an error.
:: (a -> c) | Mapping function used when first event is present. |
-> (b -> c) | Mapping function used when second event is present. |
-> (a -> b -> c) | Mapping function used when both events are present. |
-> Event a | First event |
-> Event b | Second event |
-> Event c |
A generic event merge-map utility that maps event occurrences, merging the
results. The first three arguments are mapping functions, the third of which
will only be used when both events are present. Therefore, mergeBy
=
mapMerge
id
id
Applicative-based definition: mapMerge lf rf lrf le re = (f $ le * re) | (lf $ le) | (rf $ re)
mergeEvents :: [Event a] -> Event a Source #
Merge a list of events; foremost event has priority.
Foldable-based definition: mergeEvents :: Foldable t => t (Event a) -> Event a mergeEvents = asum
catEvents :: [Event a] -> Event [a] Source #
Collect simultaneous event occurrences; no event if none.
joinE :: Event a -> Event b -> Event (a, b) Source #
Join (conjunction) of two events. Only produces an event if both events exist.
Applicative-based definition: joinE = liftA2 (,)
filterE :: (a -> Bool) -> Event a -> Event a Source #
Filter out events that don't satisfy some predicate.
gate :: Event a -> Bool -> Event a Source #
Enable/disable event occurrences based on an external condition.
Switching
Basic switchers
switch :: Monad m => SF m a (b, Event c) -> (c -> SF m a b) -> SF m a b Source #
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!
dSwitch :: Monad m => SF m a (b, Event c) -> (c -> SF m a b) -> SF m a b Source #
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!
Parallel composition and switching
Parallel composition and switching over collections with broadcasting
parB :: Monad m => [SF m a b] -> SF m a [b] Source #
Spatial parallel composition of a signal function collection. Given a
collection of signal functions, it returns a signal function that broadcasts
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 https://www.antonycourtney.com/pubs/hw03.pdf
dpSwitchB :: (Functor m, Monad m, Traversable col) => col (SF m a b) -> SF m (a, col b) (Event c) -> (col (SF m a b) -> c -> SF m a (col b)) -> SF m a (col b) Source #
Decoupled parallel switch with broadcasting (dynamic collection of signal
functions spatially composed in parallel). See dpSwitch
.
For more information on how parallel composition works, check https://www.antonycourtney.com/pubs/hw03.pdf
Parallel composition over collections
parC :: Monad m => SF m a b -> SF m [a] [b] Source #
Apply an SF to every element of a list.
Example:
>>>
embed (parC integral) (deltaEncode 0.1 [[1, 2], [2, 4], [3, 6], [4.0, 8.0 :: Float]])
[[0.0,0.0],[0.1,0.2],[0.3,0.6],[0.6,1.2]]
The number of SFs or expected inputs is determined by the first input list, and not expected to vary over time.
If more inputs come in a subsequent list, they are ignored.
>>>
embed (parC (arr (+1))) (deltaEncode 0.1 [[0], [1, 1], [3, 4], [6, 7, 8], [1, 1], [0, 0], [1, 9, 8]])
[[1],[2],[4],[7],[2],[1],[2]]
If less inputs come in a subsequent list, an exception is thrown.
>>>
embed (parC (arr (+1))) (deltaEncode 0.1 [[0, 0], [1, 1], [3, 4], [6, 7, 8], [1, 1], [0, 0], [1, 9, 8]])
[[1,1],[2,2],[4,5],[7,8],[2,2],[1,1],[2,10]]
Discrete to continuous-time signal functions
Wave-form generation
hold :: Monad m => a -> SF m (Event a) a Source #
Zero-order hold.
Converts a discrete-time signal into a continuous-time signal, by holding the last value until it changes in the input signal. The given parameter may be used for time zero, and until the first event occurs in the input signal, so hold is always well-initialized.
>>>
embed (hold 1) (deltaEncode 0.1 [NoEvent, NoEvent, Event 2, NoEvent, Event 3, NoEvent])
[1,1,2,2,3,3]
Accumulators
accumBy :: Monad m => (b -> a -> b) -> b -> SF m (Event a) (Event b) Source #
Accumulator parameterized by the accumulation function.
accumHoldBy :: Monad m => (b -> a -> b) -> b -> SF m (Event a) b Source #
Zero-order hold accumulator parameterized by the accumulation function.
State keeping combinators
Loops with guaranteed well-defined feedback
loopPre :: Monad m => c -> SF m (a, c) (b, c) -> SF m a b Source #
Loop with an initial value for the signal being fed back.
Integration and differentiation
integral :: (Monad m, Fractional s, VectorSpace a s) => SF m a a Source #
Integration using the rectangle rule.
integralFrom :: (Monad m, Fractional s, VectorSpace a s) => a -> SF m a a Source #
Integrate using an auxiliary function that takes the current and the last input, the time between those samples, and the last output, and returns a new output.
derivative :: (Monad m, Fractional s, VectorSpace a s) => SF m a a Source #
A very crude version of a derivative. It simply divides the value difference by the time difference. Use at your own risk.
derivativeFrom :: (Monad m, Fractional s, VectorSpace a s) => a -> SF m a a Source #
A very crude version of a derivative. It simply divides the value difference by the time difference. Use at your own risk.
Starts from a given value for the input signal at time zero.
iterFrom :: Monad m => (a -> a -> DTime -> b -> b) -> b -> SF m a b Source #
Integrate using an auxiliary function that takes the current and the last input, the time between those samples, and the last output, and returns a new output.
Noise (random signal) sources and stochastic event sources
:: MonadRandom m | |
=> Time | The time q after which the event should be produced on average |
-> b | Value to produce at time of event |
-> SF m a (Event b) |
Stochastic event source with events occurring on average once every tAvg 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.
Execution/simulation
Reactimation
reactimate :: Monad m => m a -> (Bool -> m (DTime, Maybe a)) -> (Bool -> b -> m Bool) -> SF Identity a b -> m () Source #
Convenience function to run a signal function indefinitely, using a IO actions to obtain new input and process the output.
This function first runs the initialization action, which provides the initial input for the signal transformer at time 0.
Afterwards, an input sensing action is used to obtain new input (if any) and the time since the last iteration. The argument to the input sensing function indicates if it can block. If no new input is received, it is assumed to be the same as in the last iteration.
After applying the signal function to the input, the actuation IO action is executed. The first argument indicates if the output has changed, the second gives the actual output). Actuation functions may choose to ignore the first argument altogether. This action should return True if the reactimation must stop, and False if it should continue.
Note that this becomes the program's main loop, which makes using this
function incompatible with GLUT, Gtk and other graphics libraries. It may
also impose a sizeable constraint in larger projects in which different
subparts run at different time steps. If you need to control the main loop
yourself for these or other reasons, use reactInit
and react
.
Debugging / Step by step simulation
evalAtZero :: SF Identity a b -> a -> (b, SF Identity a b) Source #
Evaluate an SF, and return an output and an initialized SF.
WARN: Do not use this function for standard simulation. This function is intended only for debugging/testing. Apart from being potentially slower and consuming more memory, it also breaks the FRP abstraction by making samples discrete and step based.
evalAt :: SF Identity a b -> DTime -> a -> (b, SF Identity a b) Source #
Evaluate an initialized SF, and return an output and a continuation.
WARN: Do not use this function for standard simulation. This function is intended only for debugging/testing. Apart from being potentially slower and consuming more memory, it also breaks the FRP abstraction by making samples discrete and step based.
evalFuture :: SF Identity a b -> a -> DTime -> (b, SF Identity a b) Source #
Given a signal function and time delta, it moves the signal function into the future, returning a new uninitialized SF and the initial output.
While the input sample refers to the present, the time delta refers to the future (or to the time between the current sample and the next sample).
WARN: Do not use this function for standard simulation. This function is intended only for debugging/testing. Apart from being potentially slower and consuming more memory, it also breaks the FRP abstraction by making samples discrete and step based.