Functional Reactive Programing with Elm: Part IV - Elm Signals
Reactive programs require us to write programs that respond to a stream of “events”. Additionally, making these programs composable allows us to use all our functional programming techniques to manipulate makes these programs - Functional Reactive Programming (FRP).
The first concept that is needs is how do we represent a stream of events? Just like List a
represent an extensible structure containing elements of type a, we introduce Signal a
which contains an extensible structure which contains the type a.
In some sense there is a relationship/duality between extensible structures (Lists) in space (memory) and Streams of events (Signals) that are extensible structures in time.
A Signal is a special type that represents a set of values changing over time type Signal a
.The coordinates representing the location of the mouse (Mouse.position) and the keys pressed on the keyboard are examples of Signals.
Our Reactive programs contain “networks” of such signals that we process using some special functions that can process Signals. Here are the some key functions (defined in the Signal Module):
- Map
map : (a -> b) -> Signal a -> Signal b
- Merge
merge : Signal a -> Signal a -> Signal a
- Manage state:
foldp : (a -> s -> s) -> s -> Signal a -> Signal s
- Filter
filter : (a -> Bool) -> a -> Signal a -> Signal a
Understanding how to use all these functions and getting familiar with some standard Signals like the mouse position (Mouse.position : Signal (Int, Int)
) and keyboard presses (Keyboard.presses : Signal KeyCode
) should take us a long way towards a clear understanding of FRP.
Mouse Signals
Try this out by pasting this code in the online editor/runner.
Let us see if we can understand this code. Mouse.position is a Signal that contains a tuple of Integers which are the mouse coordinates, we want to show
each of these tuples.
Just like we can use the function map for lists:
map : (a -> b) -> List a -> List b
, the Signal module defines a map function that maps a function over a Signal: map : (a -> b) -> Signal a -> Signal b
.
So that is all we need to do map show over the Mouse position Signal using Signal.map! (Amazing how close the code in the program is to this description in the text.)
Now let us look at how to write a simple function that operates on two values and map it over two Signals.
This should give you a clear picture of how we can take normal functions like combine
that we can define and lift/map them over Signals.
Keyboard Signals
Now let us look at some programs that manipulate Keyboard events.
Here we will display the Signals generated form the 4 arrow keys.
Another simple program that manages the keyboard signals from the standards keys.
Easy enough, and I think that we can see a pattern emerging of how we write programs to process Signals.
Managing state
Now that we are familiar with managing Signals, we can look at how do we manage state. So far we just dealt with events as they arise and routed them through our programs. So we did not have and acton that depended on the previous state of a Signal.
If for example, we want to create a program to count the mouse clicks that have happened so far, then we need to keep a running count of the clicks that have happened. Then increment this count every time a new click happens.
To functional programers they have already encountered a similar problem with lists. Foe example, How do you sum the elements for a list? You have to take an initial value for the sum and then increment this value as we process each element in the list. This is done using a function fold. We covered an example of this in the post on Elm basics.
There is a function similar to fold that works for signals called foldp (for fold over the past?) Looking at the signature:
foldp : (a -> s -> s) -> s -> Signal a -> Signal s
we see that foldp
takes a function and a initial state
and updates this initial state every time an event happens.
This idea becomes one of the main pattern in functional reactive pograms for managing state.
Okay here is a simple program that illustrates this.
This code is a bit verbose because I explicitly created a counter and the increment
function. Shown below is the code that more experienced functional programmers will write.
Note that in this example increment uses only the counter value and just increments it. We are not look at the state of the Signal, we just need to know that it happened in this example. We do need two arguments as we are going to fold over the counter and the signal.
In other examples we will use the values stored in the signal.
Like most good functional programs this one is mesmerizing in its conciseness and simplicity!
Exercises
- Try and make a counter that counts the number of seconds since the program is launched. Hint: you can use the function
fps : number -> Signal Time
which is one of the tickers defined in the module Time. See the documentation here..
- Write a program that counts the number of key presses.
- Write a program that builds up a string of all the keys that are pressed.
- Write a simple clock program that displays the current time. The code below is not very pretty, add a background and style the display to make a pretty clock.
- Can you construct a program that takes the mouse position,runs it through two different functions offSetPosition and relativePosition and then displays both results. (This exercise was suggested to me by Vinay, and it really illustrates how difficult it is to work with Signals until you have mastered the Signal idioms.)
Or you can use the combine function in the package Signal.Extra which has a few nice additional functions.
- Can you display a list of Signals? Use 4 Mouse Signals as an example.
If you have not already done this before please take a look at this great article explaining Signals on elm-lang.
Mailboxes
The next topic to look at is using Mailboxes and Tasks.
Here is a simple example of using a Mailbox to handle button clicks on a web page.
###Exercise
- Can you extend the previous message to two buttons that display different messages in the same text field?
- Can you update two different fields when two buttons are clicked?
Notice how we are very close to the class Model/View controller pattern here. We will revisit this in more detail when discuss web applications and Web Architecture.
Tasks - Asynchronous Operations
The next ingredient that we need is running processes Asynchronously. This is done using Tasks in Elm.
Retrieving data from a web page
Tasks calling a service
In this section we will review the process of using tasks to communicate with a service asynchronously.
Effects
One final topic and we are done with processing Signals.