After playing with Flapjax library in Javascript, I moved to Reactive to
learn more about FRP. Because research on Functional Reactive
Programming is most active in Haskell, I thought it would be better to
do that. Reactive seems to be a nice library, but unfortunately I
couldn't find many working code examples. So I show some of them as my
exercise. To write this, I owe a maoe's great
article in Japanese.
As I didn't have much time, I couldn't write a good explanation
now. But still I hope it helps some people who learn Reactive like
me. I used Haskell Platform 2010 (slightly old) and did cabal install reactive --enable-documentation to install Reactive.
The first example shows "Hello, World!" after three
seconds. atTime generates a timer event, and <$>
convert this event to IO action (\_ -> putStrLn "Hello, World!") which
writes a string.
This is as same as above, but it makes events each second.
This makes running Fibonnaci numbers. You can use scanlE
to process previous value and current value of the event in a
function. In this case, (0, 1) is the initial value, and when
an event occurs, the function \(n0, n1) _ -> (n1, n0 + n1) calculates
next value, and the result (the first case is (1, 1)) is used as a
next argument when a new event occurs.
It shows characters as you type. It looks difficult but you don't
have to worry about run function. The important part is
machine :: Event Char -> Event (IO ()) that convert a
character input event to an IO action.
This example shows how to merge two events. onType is same
as machine in the previous example, and onClock is
same as helloMany.hs example. I used `mappend` to
merge the two events
This shows a simple state machine. The function next
defines the state machine, and mealy_ convert the definition
to an event. zipE is another way to merge two events. Unlike
mappend, you can see two values in the two events in a same
time.
Functional Reactive Programming (FRP) is a framework to deal with
time-varying data in a clean way. It is a combination of beauty of
functional programming and dynamics of object oriented
programming. The basic principle is easy enough as spreadsheets,
however, its vague scope and arcane terminologies keep you from
grasping it. It's not quite easy to answer the question such as what
makes FRP different from Observer Pattern, Data Flow, etc ??. I think
a good way to explain FRP is to compare FRP library against non-FRP
library, and I could show you where FRP is special, and pros-and-cons
of FRP.
I examined Flapjax as an example of FRP, and took Bred Victor's
Tangle as the comparison target. Although Tangle has similar goal of
FRP as he wrote "Tangle is a library for creating reactive documents",
its implementation is quite different from Flapjax.
Flapjax
Side-effect is hidden inside the framework. Time-varying data is
represented by dependent tree, and you can compose those trees to
implement a complex behavior.
Tangle
Tangle provides a simple framework and UI widgets, but the data
flow is represented by a normal imperative programming and
assignments.
Because of those properties, I think comparing the two libraries is
helpful to understand what FRP is. I hope it makes clear idea about
FRP in your mind.
Simple Calorie Calculator in Tangle
This is the first example from the Tangle's documentation. You can
modify the number of cookies by dragging, and it keeps calculating the
calories as you change the value.
When you eat cookies,
you will consume calories.
To make this nice reactive document. This document consists with two parts,
HTML for the view and javascript for the model.
<p id="tangle"
When you eat <span data-var="cookies" class="TKAdjustableNumber" data-min="2" data-max="100"> cookies</span>,
you will consume <span data-var="calories"></span> calories.
</p>
The HTML part is straightforward, this is just a normal HTML except
special attributes for Tangle. Data-var is used to connect
HTML elements to Tangle object's
properties. Class name TKAdjustableNumber makes a draggable input
control. Data-min and data-max are its
parameters.
var element = document.getElementById("tangle");
new Tangle(element, {
initialize: function () {
this.cookies = 4;
},
update: function () {
this.calories = this.cookies * 50;
}
});
The actual model of the document is described in the second argument
of Tangle object's constructor (new Tangle). It consists with
just two parts. initialize sets up the initial state,
and update is invoked whenever you modify the input value.
Tangle connects the model and the HTML element specified
by getElementById("tangle").
This initialize-update structure is fairly common among end-user
programming language like Processing and Arduino.
Simple Calorie Calculator in Flapjax
Let's move on to Flapjax. Unfortunately, Flapjax doesn't have a
nice input widget as Tangle has. Instead, we use a traditional input
field. But other than that, the behavior is identical.
When you eat cookies,
you will consume calories.
As Tangle, the Flapjax version has HTML part and Javascript
part. Note that Flapjax provides "Flapjax Syntax" which allows you to
write a simpler notation, but we don't use it because I want to
compare those as Javascript libraries.
<p id="flapjax" class="example">
When you eat <input id="cookies" value="4" /> cookies,
you will consume <span id="calories"></span> calories.
</p>
Flapjax's HTML part is similar as Tangle's. The element identifiers
(cookies and calories) are given by
id attributes. Unlike Tangle, the initial number of cookies is written
in the input field.
var behavior = extractValueB("cookies");
var colories = behavior.liftB(function (n) { return n * 50; });
insertDomB(colories, "calories");
In Flapjax, time-varying data is called behavior. The goal
of the program is to make a behavior which always calculates calories
of the cookies. It's not so difficult than it
seems. ExtractValueB creates a behavior from a form element,
in this case, extractvalueB("cookies") tracks every changes
happening in the input field named "cookies". This created
behavior is processed by the function at the argument
of liftB, in this case, whenever you modify
"cookies" field, colories represents a value which
is always 50 times by the number of cookies.
Eventually, insertDomB insert the content
of colories where HTML element
"calories" is and the calories are shown on the
screen. This element is automatically updated.
Unlike Tangle, there is no side-effect in the program. One
advantage of FRP is that you are not confused between old values and
new values. In Tangle's example, this.cookies is old value
(input) and this.calories is new value (output). But you are
free to be mixed up those. In Flapjax, a new value is always the
return value of a function, and there is no chance to be mistaken.
Implement Adjustable Number Widget in Flapjax
One of advantages of FRP is its composability. You can make a
complicated behavior by combining simple behaviors (occasionally,
imperative programming gives you a hard time for debugging if the bug
involves with connected program modules with side-effects). To
demonstrate this feature, I will show you how to make a Tangle-style
draggable widget in Flapjax. This problem is particularly interesting
because processing drag and drop involves a state machine, but a state
machine is not quite fit with a functional programming style. So you
might find pros and cons of FRP clearly from this example.
When you eat cookies,
you will consume calories.
The HTML part is almost identical except adjustable class in
the input field which points a Tangle like (but not fashionable
enough) stylesheet.
<p id="flapjax-drag" class="example">
When you eat <input id="cookies-drag" value="4" class="adjustable"/> cookies,
you will consume <span id="calories-drag"></span> calories.
</p>
The main Javascript part is also similar as above. But in this
time, we are implementing makeAdjustableNumber to make a draggable
widget from the element named "cookies-drag".
var element = document.getElementById("cookies-drag");
var behavior = makeAdjustableNumber(element);
var colories = behavior.liftB(function (n) { return n * 50; });
insertDomB(colories, "calories-drag");
A drag gesture consists of three events, mousedown, mousemove, and
mouseup. After a mousedown is detected, it has to track mousemove
events to know how far you are dragging. You can make such a state
machine to construct a higher order event stream. Here are two
new concepts. An event stream is similar as behavior, but it is
a stream of discrete events instead of continuous values. But you
don't have to worry about that. It's just another object which has
slightly different API. A higher order event stream is an event
stream of event streams. This is used to make a stream which behavior
is switched depends on the input.
This mouseDownMove makes a higher order event stream that
tracks mousedown and
mousemove. extractEventE(element,"mousedown") extracts
mousedown event in the element. When the event signaled, the function
inside the mapE is evaluated. MapE is similar
as liftB but it is only for an event stream. Inside the
function, extractEventE(document,"mousemove") find mousemove
events and track the distance from mousedown. Note that I
used document to find the event because occasionally you drag
a mouse to outside the widget.
function mouseDownMove (element) {
return extractEventE(element,"mousedown").mapE(function(md) {
var initValue = parseInt(element.value);
var offset = md.layerX;
return extractEventE(document,"mousemove").mapE(function(mm) {
var delta = mm.layerX - offset;
return Math.max(1, Math.round(delta / 20 + initValue));
});
});
}
We need to handle mouseup event also. The mouseUp
function returns a higher order event stream that find mouseUp event
and the zeroE happily does nothing.
And these two event stream make by mouseDownMove
and mouseUp are going to be merged by the
mouseDownMoveUp function to complete a mousedown, mousemove,
and mouseup cycle. MergeE is used to merge two events
streams. We need one more step switchE to convert a higher
order stream to a nomal stream, in this case, a stream of numbers
(distance).
function mouseDownMoveUp(element) {
var downMoveUp = mouseDownMove(element).mergeE(mouseUp(element));
return downMoveUp.switchE();
}
Finally, we connect the event stream into an HTML element. Here I
did slightly dirty work. Whenever a drag gesture happens, the
element.value attribute is set. Probably
using insertDomB to make an output element is cleaner way,
but I chose this dirty way to make it simple. At the last line, the
event stream is converted to a behavior object
by startsWith. And that's how makeAdjustableNumber is
implemented.
function makeAdjustableNumber (element) {
var drag = mouseDownMoveUp(element);
drag.mapE(function(n) { element.value = n; });
return drag.startsWith(element.value);
}
Honestly, Flapjax doesn't seems to be too easy to use. But part of
the reasons might be that I chose to show a plain Javascript syntax to
introduce the mechanism. Flapjax also provides its own compiler which
provides cleaner syntax. This Flapjax syntax should improve
readability a lot. Anyway, I hope this short note helps you to grab a
brief idea of Flapjax and FRP.