Yampa-0.14.11: Elegant Functional Reactive Programming Language for Hybrid Systems
Copyright(c) Ivan Perez 2014-2022
(c) George Giorgidze 2007-2012
(c) Henrik Nilsson 2005-2006
(c) Antony Courtney and Henrik Nilsson Yale University 2003-2004
LicenseBSD-style (see the LICENSE file in the distribution)
Maintainerivan.perez@keera.co.uk
Stabilityprovisional
Portabilitynon-portable (GHC extensions)
Safe HaskellSafe-Inferred
LanguageHaskell2010

FRP.Yampa.Switches

Description

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 :: SF a (b, Event c) -> (c -> SF a b) -> SF 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

Basic switching

switch :: SF a (b, Event c) -> (c -> SF a b) -> SF 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 :: SF a (b, Event c) -> (c -> SF a b) -> SF 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 :: SF a b -> SF (a, Event (SF 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 :: SF a b -> SF (a, Event (SF 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 :: SF a b -> SF (a, b) (Event c) -> (SF a b -> c -> SF a b) -> SF 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 :: SF a b -> SF (a, b) (Event c) -> (SF a b -> c -> SF a b) -> SF 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 :: Functor col => col (SF a b) -> SF a (col 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 col => col (SF a b) -> SF (a, col b) (Event c) -> (col (SF a b) -> c -> SF a (col b)) -> SF 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 col => col (SF a b) -> SF (a, col b) (Event c) -> (col (SF a b) -> c -> SF a (col b)) -> SF 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 col => col (SF a b) -> SF (a, Event (col (SF a b) -> col (SF 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 col => col (SF a b) -> SF (a, Event (col (SF a b) -> col (SF 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

par Source #

Arguments

:: Functor 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 b c)

Signal function collection.

-> SF a (col c) 

Spatial parallel composition of a signal function collection parameterized on the routing function.

pSwitch Source #

Arguments

:: 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) 

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.

dpSwitch Source #

Arguments

:: Functor 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 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) 

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.

rpSwitch Source #

Arguments

:: 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)

Initial signal function collection.

-> SF (a, Event (col (SF b c) -> col (SF 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.

drpSwitch Source #

Arguments

:: 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)

Initial signal function collection.

-> SF (a, Event (col (SF b c) -> col (SF 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 :: [SF a b] -> SF [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 :: [SF a b] -> SF ([a], [b]) (Event c) -> ([SF a b] -> c -> SF [a] [b]) -> SF [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 :: [SF a b] -> SF ([a], [b]) (Event c) -> ([SF a b] -> c -> SF [a] [b]) -> SF [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 :: [SF a b] -> SF ([a], Event ([SF a b] -> [SF 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 :: [SF a b] -> SF ([a], Event ([SF a b] -> [SF 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.

See rpSwitchZ and drpSwitch.

For more information on how parallel composition works, check https://www.antonycourtney.com/pubs/hw03.pdf

With replication

parC :: SF a b -> SF [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]]