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 |
Switches allow you to change the signal function being applied.
The basic idea of switching is formed by combining a subordinate signal function and a signal function continuation parameterised over some initial data.
For example, the most basic switch has the following signature:
switch :: Monad m => SF m a (b, Event c) -> (c -> SF m a b) -> SF m a b
which indicates that it has two parameters: a signal function that produces an output and indicates, with an event, when it is time to switch, and a signal function that starts with the residual data left by the first SF in the event and continues onwards.
Switching occurs, at most, once. If you want something to switch repeatedly,
in general, you need to loop, or to switch onto the same signal function
again. However, some switches, explained below, are immediate (meaning that
the second SF is started at the time of switching). If you use the same SF
that originally provoked the switch, you are very likely to fall into an
infinite loop. In those cases, the use of dSwitch
or -->
may help.
Switches vary depending on a number of criteria:
- Decoupled vs normal switching (d): when an SF is being applied and a different SF needs to be applied next, one question is which one is used for the time in which the switching takes place. In decoupled switching, the old SF is used for the time of switching, and the one SF is only used after that. In normal or instantaneous or coupled switching, the old SF is discarded immediately and a new SF is used for the output already from that point in time.
- How the switching event is provided ( /r/k): normally, an
Event
is used to indicate that a switching must take place. This event can be part of the argument SF (e.g.,switch
), it can be part of the input (e.g.,rSwitch
), or it can be determined by a second argument SF (e.g,kSwitch
). - How many SFs are being handled ( /p/par): some combinators deal with
only one SF, others handle collections, either in the form of a
Functor
or a list ('[]'). - How the input is router (B/Z/ ): when multiple SFs are being combined, a decision needs to be made about how the input is passed to the internal SFs. In some cases, broadcasting is used to pass the same input to all internal SFs. In others, the input is itself a collection, and each element is passed to one internal SF (i.e., zipping). In others, an auxiliary function is used to decide how to route specific inputs to specific SFs in the collection.
These gives a number of different combinations, some of which make no sense,
and also helps determine the expected behaviour of a combinator by looking at
its name. For example, drpSwitchB
is the decoupled (d), recurrent (r),
parallel (p) switch with broadcasting (B).
Synopsis
- 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
- rSwitch :: Monad m => SF m a b -> SF m (a, Event (SF m a b)) b
- drSwitch :: Monad m => SF m a b -> SF m (a, Event (SF m a b)) b
- kSwitch :: Monad m => SF m a b -> SF m (a, b) (Event c) -> (SF m a b -> c -> SF m a b) -> SF m a b
- dkSwitch :: Monad m => SF m a b -> SF m (a, b) (Event c) -> (SF m a b -> c -> SF m a b) -> SF m a b
- parB :: Monad m => [SF m a b] -> SF m a [b]
- pSwitchB :: (Functor m, Monad m, Traversable col, Functor 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)
- 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)
- rpSwitchB :: (Functor m, Monad m, Functor col, Traversable col) => col (SF m a b) -> SF m (a, Event (col (SF m a b) -> col (SF m a b))) (col b)
- drpSwitchB :: (Functor m, Monad m, Functor col, Traversable col) => col (SF m a b) -> SF m (a, Event (col (SF m a b) -> col (SF m a b))) (col b)
- par :: (Functor m, Monad m, Functor col, Traversable col) => (forall sf. a -> col sf -> col (b, sf)) -> col (SF m b c) -> SF m a (col c)
- pSwitch :: (Functor m, Monad m, Traversable col, Functor col) => (forall sf. a -> col sf -> col (b, sf)) -> col (SF m b c) -> SF m (a, col c) (Event d) -> (col (SF m b c) -> d -> SF m a (col c)) -> SF m a (col c)
- dpSwitch :: (Monad m, Traversable col) => (forall sf. a -> col sf -> col (b, sf)) -> col (SF m b c) -> SF m (a, col c) (Event d) -> (col (SF m b c) -> d -> SF m a (col c)) -> SF m a (col c)
- rpSwitch :: (Functor m, Monad m, Functor col, Traversable col) => (forall sf. a -> col sf -> col (b, sf)) -> col (SF m b c) -> SF m (a, Event (col (SF m b c) -> col (SF m b c))) (col c)
- drpSwitch :: (Functor m, Monad m, Functor col, Traversable col) => (forall sf. a -> col sf -> col (b, sf)) -> col (SF m b c) -> SF m (a, Event (col (SF m b c) -> col (SF m b c))) (col c)
- parZ :: (Functor m, Monad m) => [SF m a b] -> SF m [a] [b]
- pSwitchZ :: (Functor m, Monad m) => [SF m a b] -> SF m ([a], [b]) (Event c) -> ([SF m a b] -> c -> SF m [a] [b]) -> SF m [a] [b]
- dpSwitchZ :: (Functor m, Monad m) => [SF m a b] -> SF m ([a], [b]) (Event c) -> ([SF m a b] -> c -> SF m [a] [b]) -> SF m [a] [b]
- rpSwitchZ :: (Functor m, Monad m) => [SF m a b] -> SF m ([a], Event ([SF m a b] -> [SF m a b])) [b]
- drpSwitchZ :: (Functor m, Monad m) => [SF m a b] -> SF m ([a], Event ([SF m a b] -> [SF m a b])) [b]
- parC :: Monad m => SF m a b -> SF m [a] [b]
Basic switching
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!
rSwitch :: Monad m => SF m a b -> SF m (a, Event (SF m a b)) b Source #
Recurring switch.
Uses the given SF until an event comes in the input, in which case the SF in the event is turned on, until the next event comes in the input, and so on.
See https://wiki.haskell.org/Yampa#Switches for more information on how this switch works.
drSwitch :: Monad m => SF m a b -> SF m (a, Event (SF m a b)) b Source #
Recurring switch with delayed observation.
Uses the given SF until an event comes in the input, in which case the SF in the event is turned on, until the next event comes in the input, and so on.
Uses decoupled switch (dSwitch
).
See https://wiki.haskell.org/Yampa#Switches for more information on how this switch works.
kSwitch :: Monad m => SF m a b -> SF m (a, b) (Event c) -> (SF m a b -> c -> SF m a b) -> SF m a b Source #
Call-with-current-continuation switch.
Applies the first SF until the input signal and the output signal, when passed to the second SF, produce an event, in which case the original SF and the event are used to build an new SF to switch into.
See https://wiki.haskell.org/Yampa#Switches for more information on how this switch works.
dkSwitch :: Monad m => SF m a b -> SF m (a, b) (Event c) -> (SF m a b -> c -> SF m a b) -> SF m a b Source #
kSwitch
with delayed observation.
Applies the first SF until the input signal and the output signal, when passed to the second SF, produce an event, in which case the original SF and the event are used to build an new SF to switch into.
The switch is decoupled (dSwitch
).
See https://wiki.haskell.org/Yampa#Switches for more information on how this switch works.
Parallel composition/switching (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
pSwitchB :: (Functor m, Monad m, Traversable col, Functor 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 #
Parallel switch (dynamic collection of signal functions spatially composed
in parallel) with broadcasting. See pSwitch
.
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
rpSwitchB :: (Functor m, Monad m, Functor col, Traversable col) => col (SF m a b) -> SF m (a, Event (col (SF m a b) -> col (SF m a b))) (col b) Source #
Recurring parallel switch with broadcasting.
Uses the given collection of SFs, until an event comes in the input, in which
case the function in the Event
is used to transform the collections of SF
to be used with rpSwitch
again, until the next event comes in the input,
and so on.
Broadcasting is used to decide which subpart of the input goes to each SF in the collection.
See rpSwitch
.
For more information on how parallel composition works, check https://www.antonycourtney.com/pubs/hw03.pdf
drpSwitchB :: (Functor m, Monad m, Functor col, Traversable col) => col (SF m a b) -> SF m (a, Event (col (SF m a b) -> col (SF m a b))) (col b) Source #
Decoupled recurring parallel switch with broadcasting.
Uses the given collection of SFs, until an event comes in the input, in which
case the function in the Event
is used to transform the collections of SF
to be used with rpSwitch
again, until the next event comes in the input,
and so on.
Broadcasting is used to decide which subpart of the input goes to each SF in the collection.
This is the decoupled version of rpSwitchB
.
For more information on how parallel composition works, check https://www.antonycourtney.com/pubs/hw03.pdf
With helper routing function
:: (Functor m, Monad m, Functor col, Traversable col) | |
=> (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 m b c) | Signal function collection. |
-> SF m a (col c) |
Spatial parallel composition of a signal function collection parameterized on the routing function.
:: (Functor m, Monad m, Traversable col, 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 m b c) | Signal function collection. |
-> SF m (a, col c) (Event d) | Signal function generating the switching event. |
-> (col (SF m b c) -> d -> SF m a (col c)) | Continuation to be invoked once event occurs. |
-> SF m a (col c) |
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.
:: (Monad m, Traversable col) | |
=> (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 |
-> col (SF m b c) | Initial collection of signal functions. |
-> SF m (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 m b c) -> d -> SF m 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 |
-> SF m a (col c) |
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.
:: (Functor m, Monad m, Functor col, Traversable 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 m b c) | Initial signal function collection. |
-> SF m (a, Event (col (SF m b c) -> col (SF m b c))) (col c) |
Recurring parallel switch parameterized on the routing function.
Uses the given collection of SFs, until an event comes in the input, in which
case the function in the Event
is used to transform the collections of SF
to be used with rpSwitch
again, until the next event comes in the input,
and so on.
The routing function is used to decide which subpart of the input goes to each SF in the collection.
This is the parallel version of rSwitch
.
:: (Functor m, Monad m, Functor col, Traversable 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 m b c) | Initial signal function collection. |
-> SF m (a, Event (col (SF m b c) -> col (SF m b c))) (col c) |
Recurring parallel switch with delayed observation parameterized on the routing function.
Uses the given collection of SFs, until an event comes in the input, in which
case the function in the Event
is used to transform the collections of SF
to be used with rpSwitch
again, until the next event comes in the input,
and so on.
The routing function is used to decide which subpart of the input goes to each SF in the collection.
This is the parallel version of drSwitch
.
Parallel composition/switching (lists)
With "zip" routing
parZ :: (Functor m, Monad m) => [SF m a b] -> SF m [a] [b] Source #
Parallel composition of a list of SFs.
Given a list of SFs, returns an SF that takes a list of inputs, applies each SF to each input in order, and returns the SFs' outputs.
>>>
embed (parZ [arr (+1), arr (+2)]) (deltaEncode 0.1 [[0, 0], [1, 1]])
[[1,2],[2,3]]
If there are more SFs than inputs, an exception is thrown.
>>>
embed (parZ [arr (+1), arr (+1), arr (+2)]) (deltaEncode 0.1 [[0, 0], [1, 1]])
[[1,1,*** Exception: FRP.Yampa.Switches.parZ: Input list too short.
If there are more inputs than SFs, the unused inputs are ignored.
>>>
embed (parZ [arr (+1)]) (deltaEncode 0.1 [[0, 0], [1, 1]])
[[1],[2]]
pSwitchZ :: (Functor m, Monad m) => [SF m a b] -> SF m ([a], [b]) (Event c) -> ([SF m a b] -> c -> SF m [a] [b]) -> SF m [a] [b] Source #
Parallel switch (dynamic collection of signal functions spatially composed
in parallel). See pSwitch
.
For more information on how parallel composition works, check https://www.antonycourtney.com/pubs/hw03.pdf
dpSwitchZ :: (Functor m, Monad m) => [SF m a b] -> SF m ([a], [b]) (Event c) -> ([SF m a b] -> c -> SF m [a] [b]) -> SF m [a] [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
rpSwitchZ :: (Functor m, Monad m) => [SF m a b] -> SF m ([a], Event ([SF m a b] -> [SF m a b])) [b] Source #
Recurring parallel switch with "zip" routing.
Uses the given list of SFs, until an event comes in the input, in which case
the function in the Event
is used to transform the list of SF to be used
with rpSwitchZ
again, until the next event comes in the input, and so on.
Zip routing is used to decide which subpart of the input goes to each SF in the list.
See rpSwitch
.
For more information on how parallel composition works, check https://www.antonycourtney.com/pubs/hw03.pdf
drpSwitchZ :: (Functor m, Monad m) => [SF m a b] -> SF m ([a], Event ([SF m a b] -> [SF m a b])) [b] Source #
Decoupled recurring parallel switch with "zip" routing.
Uses the given list of SFs, until an event comes in the input, in which case
the function in the Event
is used to transform the list of SF to be used
with rpSwitchZ
again, until the next event comes in the input, and so on.
Zip routing is used to decide which subpart of the input goes to each SF in the list.
For more information on how parallel composition works, check https://www.antonycourtney.com/pubs/hw03.pdf
With replication
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]]