Sunday, April 26, 2009

Functional Reactive Programming in F# (4)

In this post, I will introduce events in FRP.

Remember, a FRP is used to model systems that have an evolution with respect to continuoustime. However the way such a system computes its output(s) may be modified at discrete moments in time.To take a simple example, suppose we want to simulate a ball moving in one direction along a horizontal axis at constant speed. Suppose also that wa have an input device with two controls, the first control sets the velocity of the ball and the second is a button that, when pressed, changes the direction of the ball. Moreover, the ball is only allowed to move between 2 vertical walls. Each time the ball hits one of these walls, its velocity changes direction.This system behaves continuously until one of the three following events occur:

The user changes speed.

The user presses the velocity direction button.

The ball hits a wall.

The first two events are what I called external events. External events are generated from outside of the system. They do not depend on a particular state of the system. They can occur at any moment in time.The third event is not generated by an external source but occurs when the state of the system (here, the ball's position) satisfies some condidtions. I call such events internal events.

I will not propose to implement the above case with the ball and the walls since this is still a bit too complex for the moment and would hide some important concepts. That is why I prefer an simpler example of a colored Behavior that is white at the beginning of its life and becomes black after some time (namely 10 units of time - i.e seconds).As usual, let's start the "hard way" by manually coding all the logic. Helpful combinators will be derived from this first coding.

As always, the name of the game is to find abstractions (that is, combinators) to help us writing such event handling logic.

The next step is to move the event detection outside of the Behavior function. This is done by defining a cond function that returns None if time is less than 10 seconds and Some blackB when time is higher than or equal to 10 seconds.

The cond function acts as a sort of event generator for colorB.

Now, the Behavior function (bf) invoques cond and tests whether it returns None of Some newBehavior. If cond returns None, then the colorB Behaviors stays the same else colorB becomes the new Behavior returns by cond (newBehavior).
We can ask ourselves what we have gained here. The main advantage is that the logic of producing the event is now separated from the handling of the event.

Moreover the handling of the event is always the same and does not depends on a particular event:
If cond is None, then we do nothing special
if cond is Some newBehavior then we replace the current Behavior by newBehavior.

The next step is more fundamental. The cond function depends on time. But we already have things that depends on time, namely Behaviors.
So why cond could not be a Behavior?
If fact, cond can well become a Behavior. One big advantage of this is that cond functions may have internal state, just like any Behavior.

It is still possible to increase the abstraction level. Up to now createColor explicitly depends on the value White. But taking a closer look to what we want, we can conclude that colorB follows the rules:

Before some time t0 (10 seconds here) colorB is equal to a constant Behavior whose value is White.

After that time, colorB should be equal to the Behavior blackB.

The switchB function captures the essence of what we want: switchB takes a Behavior as first argument and a condition Behavior as second. switchB returns a Behavior that is equal to its first argument until the condition Behavior returns some new Behavior. At this point, the initial Behavior is replaced by the new one.