Yampa is a pretty cool arrow-based framework for writing FRP programs in Haskell. However, its documentation is sorely lacking. There is a limited amount of information on the Haskell wiki, and the Haddock documentation on Hackage lists function after function (often with obscure names) with no detail as to its use – other than the type signature. I plan to remedy this.
I have already started to expose some of the “hidden” documentation in the source code, and add some extra details where able – but there are still lots of gaps to be filled. For example, on the wiki there are a number of pages explaining a selection of Yampa’s different “switch” functions – which are core to making the program “react” to events. The page regarding the standard
switch is the only one with any documentation other than the type signature and a diagram. As far as the rest go, the standard
switch is relatively intuitive but it’s only one of a few with any documentation anywhere: in the wiki, on Hackage, or in the source code.
In order to demonstrate these functions I will be using the commonly used falling ball example as a starting point. It’s based on the example used in Jekor’s “Code Deconstructed” episodes on Cuboid. In the following code, a ball (represented by the type
Ball) falls through space from a standstill under the effect of gravitational acceleration (9.81m/s2). The initial state of the ball is given by the top-level value
ball. The signal function
fallingBall takes the initial state and integrates the effect of gravity on the ball’s velocity; and the effect of the ball’s velocity on its position.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
We will now extend this example with a signal function which makes the ball bounce when it reaches y=0. To do this, the effect of the signal function much be changed by using a switch.
In Yampa, signal functions can be swapped-out in response to events; this facilitates the “reactive” part of functional reactive programming. Switch functions create the signal functions which are able to do this. There are a few different basic switch functions: the standard
switch, the recurring
rSwitch, and the “call-with-current-continuation”
kSwitch. There are also parallel switches which won’t be discussed in this blog post. Every switch function also has a “delayed observation” counterpart which is prefixed with a
d. The switching events of these functions are non-strict and their effects are not immediately observable.
We will now use each type of switch in turn to demonstrate how they work and how they differ from each other.
The standard basic switch has the following type:
1 2 3
If you’re new to Yampa, this could be fairly intimidating but it’s really quite simple. The wiki has this to say about it:
A switch in Yampa provides change of behavior of signal functions (SF) during runtime. The function ‘switch’ is the simplest form which can only be switched once. The signature is read like this: “Be a SF which is always fed a signal of type ‘in’ and returns a signal of type ‘out’. Start with an initial SF of the same type but which may also return a transition event of type ‘Event t’. In case of an Event, Yampa feeds the variable ’t’ to the continuation function ‘k’ which produces the new SF based on the variable ’t’, again with the same input and output types.”
Here is an example of
switch in use.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
One of Yampa’s more interesting features is the ability to “freeze” a signal function and reactivate it later.
kSwitch is a switch function which gives you the ability to keep the old signal function in a frozen state to use later.
1 2 3 4
This type signature is a little more complicated, lets break it down:
- The first argument is a signal function is the one that you want to switch
- The second signal function acts as a trigger, reading the first signal function’s input and output, and outputting an event when it wants to initiate the switch
- The third argument is a function which generates the signal function to switch to. It takes the old signal function (frozen in state) and the event’s value as its arguments
Other that having access to the old signal function, the other advantage of
switch is that it doesn’t require that the signal function being switched is kept separate from the one generating the event. The same functionality implemented above can be re-implemented with
kSwitch as follows:
1 2 3 4 5 6 7 8 9 10
In the source code,
rSwitch is described as a “recurring switch”. As opposed to
rSwitch takes a signal function and produces a modified one which, in addition to having it’s usual input value also takes an event tagged with a new signal to switch to. This is the type signature:
This arrangement makes switching less flexible because the event which triggers the switch cannot come from the signal function being switched. This makes an implementation of
rSwitch hard, if not impossible (I have not found a solution). However
rSwitch does have its uses. For example, to cycle through a list of signal functions, change every n seconds, there is a very elegant solution using
1 2 3 4
In this example,
afterEach takes a list of
(Time, a) pairs and produces an
Event a after the duration specified with each value. Used in conjunction with
Prelude (which loops a finite list to make an infinite list), the cycle can continue forever.
Another thing to note is that after the switch event, the switch is still in place – ready to accept any new signal functions. This is why it’s a “recurring” switch.
kSwitch on the other hand require that the new signal function have its own switch defined to make the behavior repeat.
Yampa’s basic switches provide three different methods of changing the running signal function in response to events:
- The simple
switchwhich requires the signal function being switched provide an event in addition to the normal output
- The “call-with-current-continuation”
kSwitchwhich has a separate “trigger” signal function, and provides the frozen state of the old signal function which can be re-used later
- The repeating
rSwitchwhich takes the new signal function from the event triggering the switch