The Yale FRP User's Manual

1  Introduction

FRP (which stands for functional reactive programming) is a domain specific language (DSL) embedded in Haskell that is used for programming hybrid reactive systems. It can be further tailored to specific applications, yielding DSLs for graphics and animation (Fran), robotics (Frob), and computer vision (FVision). In this document the emphasis is on robotics.

FRP allows interaction with the outside world to be expressed in a concise, declarative manner without resorting to the IO monad. FRP is independent of the underlying application and is easy to incorporate into new interactive applications.

The ideas underlying FRP were originally developed by Conal Elliott in the context of graphics and animation []. There are, however, subtleties in the language design and implementation, and thus we refer to the version described here as Yale FRP. It is based on a stream-based implementation of FRP developed by Paul Hudak in the book The Haskell School of Expression: Learning Functional Programming through Multimedia (SOE) [], but it also includes significant library modules taken from Conal Elliott's original Fran implementation.

Yale FRP is "embedded" in the functional language Haskell. Thus on one hand it can be viewed as nothing more than a Haskell library. But Haskell's flexible type system, higher-order functions, and lazy evaluation permit a design that has the look and feel of a very different sort of language. Nevertheless, learning Haskell is a prerequisite to programming in FRP. Readers should refer to introductory Haskell material such as A Gentle Introduction to Haskell [], which is most useful to those who have already programmed in a functional language before, or to the SOE textbook []. In the interest of completeness, this manual includes a very brief overview of Haskell. Examples are drawn from animation and robotics.

Installing Yale FRP

Yale FRP is implemented as a Haskell library, so you must have a Haskell implementation installed before you can install FRP. There are many Haskell implementations available, all of which are free. We recommend Hugs (http://haskell.org/hugs) above others because it is easy to use, being an interactive interpreter, and because Yale FRP was developed under Hugs, so we expect this combination to be most robust. The GHC Haskell compiler (http://haskell.org/ghc) can also be used, and is critical for compute-intensive applications, but it is harder to use, and does not yet support the graphics library.

Yale FRP also uses the Haskell graphics library, which is not part of the standard Hugs distribution. It can be downloaded from http://haskell.org/graphics.

Yale FRP itself can be downloaded from http://haskell.org/frp as a gzipped file, which should be unpacked into a new directory, say ~/frp. Then add ~/frp to Hugs' search path (you know how to do this if you installed the graphics library yourself; otherwise check the Hugs user's manual).

In the remainder of this manual we will refer to Yale FRP simply as FRP, and we assume that you are using the Hugs implementation of Haskell.

2  An Introduction to Haskell

Since FRP inherits the syntax, type system, and libraries of Haskell, users of FRP must, of necessity, learn about Haskell. This section contains a very brief overview Haskell; those familiar with Haskell may wish to skip to the next section.

The essential Haskell features for FRP programming include:

Of less interest are the module system, the IO system, list comprehensions, arrays, patterns, and defining new type classes.

2.1  Reading The Examples

To help understand examples in this guide we include a brief explanation of some essential features.

Predefined Haskell Functions and Data Types

Haskell provides a special module called the Prelude to define a wide variety of built-in functions and data types, some of which are used extensively in the examples in this manual. They are listed here, although some have already been discussed.

Data types:
data Integer     = ... Infinite precision integer
data Int         = ... 32 bit Integer
data Double      = ... double prec floats
data Bool        = True | False
data Char        = ... a single character
type String      = [Char]         -- a string is just a list of chars
data [a]         = [] | a : [a]   -- lists
data (a,b)       = (a,b)          -- tuples

Functions:
fst        :: (a,b) -> a   -- first component of a 2-tuple
fst (x,y)  =  x
snd        :: (a,b) -> b   -- second component of a 2-tuple
snd (x,y)  =  y
($)        :: (a -> b) -> a -> b
f $ x      =  f x          -- infix function application
(.)        :: (a -> b) -> (b -> c) -> (a -> c)
f . g      = \x -> g (f x) -- composition

Types in Haskell are quite expressive; understanding and exploiting their utility is highly encouraged. Although types in Haskell are inferred, allowing type signatures to be omitted, it is standard practice to write explicit type signatures for every top-level function (as above), which aids clarity, forces one to understand well the structure of a program, and makes type errors easier to diagnose. In fact type signatures are so useful that it is sufficient for a manual such as this to describe functions by simply giving their type signatures and providing an informal description of how they behave.

3  FRP Basics

In this section the basic ideas underlying FRP are introduced.

3.1  Signals: Behaviors, Events, and Clocked Events

In FRP, most values evolve over time. Continuously varying values (i.e. defined at every point in time) are called behaviors. Discretely varying values (i.e. defined only at specific points in time) are called Events. A behavior or event that yields values of type a in the presence of input i has type Behavior i a or Event i a, respectively.

For example, consider a robot equipped with a set of sonars for navigation. At regular intervals, the robot samples these sonars, yielding a list of proximity values:
type Length = FRPReal

... :: [Length]  -- a list of distance values
where FRPReal defines a generic floating point type (usually Double). Rather than deal with these sonar samples individually, Frob captures them as a stream of discrete values; specifically, as an event of type Sonar i => Event i [Length], where i is the "input" to the overall process: a record that contains the readings of all of the robot's sonars (and possibly other sensors). The constraint Sonar i says: whatever i is, it has to contain sonar readings.
sonarReadings  :: Sonar i => Event i [Length]

Event values are available only at specific points in time. Behaviors, however, are defined at all points in time. Conversion from discrete to continuous time requires interpolation. The simplest interpolation scheme is "sample and hold": that is, return the value of the most recent event. The stepB function implements this sample-and-hold strategy:
stepB  :: a -> Event i a -> Behavior i a

Since behaviors have value at all times, we can compose them pointwise, a property that events do not hold in general:
vl, vr :: Behavior Sensors Vector2
-- velocities of left and right wheels of a differential drive robot

vc :: Behavior Sensors Vector2
-- velocity of the center of the robot

vc = (vl + vr)/2
In this example, +, / and 2 are functions and constants in the behavior world. Haskell type classes allow us to use the same symbol (e.g. +) for both the static version and the behavioral version of these values.

In general, we cannot compose two events pointwise, because they are not guaranteed to always occur at the same discrete times. This is annoying, so Frob also defines clocked events of type CEvent clock i a. The point is, if two clocked events e1 and e2 have the same clock type, then they are guaranteed to be synchronous, i.e. e1 occurs whenever e2 occurs. This useful property allow us to safely apply lifted functions to synchronous events. For example,
leftSonarE, rightSonarE :: Sonar i => CEvent SonarClock i Length
leftSonarE  = ... -- reading of the left sonar, i.e. distance to left wall
rightSonarE = ... -- reading of the right sonar, i.e. distance to right wall

pathWidthE :: Sonar i => CEvent SonarClock i Length
pathWidthE = leftSonarE + rightSonarE
             -- width of the path ((+) is lifted here)

Haskell's type system prevents events with different clocks to be mixed, so the following code generates a type error:
frontSonarE :: Sonar  i => CEvent SonarClock  i Length
bumperE     :: Bumper i => CEvent BumperClock i Bool

e = bumperE ||* (frontSonarE <=* 1.0)  -- error!

3.2  Reactivity

FRP is about reactive programming, so signals need to be able to react to other signals. Switching is one way to do this.
switch :: Behavior i a -> Event i (Behavior i a) -> Behavior i a
From the type signature of switch we note that the second argument is an event that generates behavior values, something we haven't seen before.

Given a behavior b defined as:
b = b0 `switch` e
b
will start as b0, and when e occurs with behavior b1, b will behave like b1; later when e occurs and generates another behavior b2, b will switch to b2, and so on.

But how do we get something of type Event i (Behavior i a) to start with? By using the event mapping operator:
(==>) :: Event i a -> (a -> b) -> Event i b
The two arguments to (==>) are an event and a function. What (==>) does is to apply the function argument to each occurrence of the event argument. If b is a behavior type, then the result is an event of behaviors.

For a concrete example, suppose we have a synchronous drive robot for which we can only control the steering rate of the wheels (the velocity is fixed), and the robot senses the environment via an array of sonars (the sonarReadings event). Our goal is to try not to crash the robot, so we need a function that generates the right turning rate behavior given the most recent sonar reading:
type AngSpeed = FRPReal  -- angular speed

avoidObstacle :: Sonar i => [Length] -> Behavior i AngSpeed
avoidObstacle sonars = ...

In the following example, the robot goes straight at first, and then calls avoidObstacle to determine the right turning rate every time a set of sonar readings is available:
wander :: Sonar i => Behavior i AngSpeed
wander = 0                         -- Don't turn wheels initially;
           `switch` sonarReadings  -- If new readings come,
              ==> avoidObstacle    -- use the most recent sonar
                                   -- readings to calculate the
                                   -- desired turning rate

Sometimes we only want to switch the behavior on the first occurrence of the event. For this purpose we can use till:
till   :: Behavior i a -> Event i (Behavior i a) -> Behavior i a
b `till` e  = b `switch` onceE e
onceE e
keeps the first occurrence of event e but drops the rest of them.

One thing to remember is that when we switch into a behavior, that behavior is not started until the switching. But why should we care about when a hehavior is started? That's because some behaviors rely on the past (we say they are stateful in this case) and therefore can be affected by the start time. As an example, consider a robot that does not have an odometer but has a sensor for wheel velocity. We can define the odometer behavior as the integral of the wheel velocity. It is important to know when the integration begins, because all distance travelled before the integration will be ignored.

We would also like to be able to switch events, so switch and till are overloaded to handle events as well:
switch   :: Event i a -> Event i (Event i a) -> Event i a
till     :: Event i a -> Event i (Event i a) -> Event i a

4  Using FRP

4.1  The Structure of FRP

FRP largely consists of the following modules:

To use FRP, import the appropriate top-level module:

As you gain more experience with Haskell, you are encouraged to read the FRP source files, which no doubt will deepen your understanding of the system. Besides, the source code is (almost) always the best documentation.

4.2  Your First FRP Program

The best way to learn programming is to actually program, and we're sure you just can't wait to see FRP in action, so let's get started. First, create a file Hello.hs with the following content:
import SimuGraphicsFRP

main = teximate_ $ constB "Hello, world!"

Then fire up Hugs with the -98 switch, which allows some non-standard features of Haskell to be used and is required to run FRP:
$ hugs -98 Hello

Now your program should be properly loaded. If you see any error message, make sure the directory where Hugs is intalled is in your search path and the file Hello.hs is in the current directory.

Next type main<return> at the Hugs prompt:
Main> main
and you should see a screen full of the text "Hello, world!". Congratulations! You have just successfully run your first FRP program! Although this example is not very sophisticated, soon you will be able to write more powerful and interesting application in FRP.

To interrupt the execution, simply press Control-C. Then you can type ":q" to quit Hugs.

5  Learning the FRP Language

In this section you will find the definition of the FRP language. You are not expected to read this part line by line. Instead, skim it and come back later when need arises.

5.1  Types in FRP

Types in FRP are divided into static types and signal types. The former are for values that do not change over time, and the latter are for behaviors and events.


-- static types
type FRPReal     = Double
type Time        = FRPReal        -- in seconds
type DeltaTime   = FRPReal        -- Time deltas, i.e. durations
type Angle       = FRPReal        -- in radians
data Point2      = Point2XY FRPReal FRPReal
data Point3      = Point3XYZ FRPReal FRPReal FRPReal
data Vector2     = Vector2XY FRPReal FRPReal
data Vector3     = Vector3XYZ FRPReal FRPReal FRPReal
data Transform2  = ... 2D Transformation Function
data Transform3  = ... 3D Transformation Function

-- signal types
data Behavior     input a   = ...
data Event        input a   = ...
data CEvent clock input a   = ...

Events and clocked events are collectively called generic events, a concept captured by the GEvent class:
instance GEvent    Event
instance GEvent    (CEvent clock)

5.2  Lifting Functions

FRP distinguishes itself from most other reactive programming languages by having first-class time-varying values (signals). Static functions may be lifted into the signal domain, yielding a function that operates pointwise on its behavior arguments to return a behavior result. In FRP there are a family of functions that perform this lifting operation, one for each function arity. Hence the following type class:
class Functor z => Zippable z where
  ($*)         :: z (a -> b) -> z a -> z b
  lift0        ::                      a  -> z a
  lift1        ::                (a -> b) -> z a -> z b
  lift2        ::           (a -> b -> c) -> z a -> z b -> z c
  lift3        ::      (a -> b -> c -> d) -> z a -> z b -> z c -> z d
  lift4        :: (a -> b -> c -> d -> e) -> z a -> z b -> z c -> z d -> z e
  -- and so on

The name Zippable is picked because lifted functions "zip" together different signals. There are two instances of Zippable:
instance Zippable (Behavior input)
instance Zippable (CEvent clock input)

5.3  Lifted Prelude Functions

Most numeric functions are available in lifted form via overloading. Just use the same function name for continuous behaviors that you use in the static world. In particular, all methods in the classes Num, Franctional, Floating, and Integral are overloaded to work on behaviors (see the Haskell 98 Report for a list of these methods). Examples: ...

Many pre-defined Haskell functions, however, are not methods in existing classes, and thus FRP defines its own lifted versions of them. By convention, the suffix Z denotes a lifted function, and the suffix * denotes a lifted infix operator.


-- 2-tuples (pairs)
pairZ        :: Zippable z => z a -> z b -> z (a,b)
fstZ         :: Zippable z => z (a,b) -> z a
sndZ         :: Zippable z => z (a,b) -> z b
pairZSplit   :: Zippable z => z (a,b) -> (z a, z b)

-- 3-tuples
tripleZ      :: Zippable z => z a -> z b -> z c -> z (a,b,c)
fst3Z        :: Zippable z => z (a,b,c) -> z a
snd3Z        :: Zippable z => z (a,b,c) -> z b
thd3Z        :: Zippable z => z (a,b,c) -> z c
tripleZSplit :: Zippable z => z (a,b,c) -> (z a, z b, z c)

-- lists
nilZ         :: Zippable z => z [a]
consZ        :: Zippable z => z a -> z [a] -> z [a]
headZ        :: Zippable z => z [a] -> z a
tailZ        :: Zippable z => z [a] -> z [a]
nullZ        :: Zippable z => z [a] -> z Bool
(!!*)        :: Zippable z => z [a] -> z Int -> z a
zListToListZ :: Zippable z => [z a] -> z [a]
liftL        :: Zippable z => ([a] -> b) -> ([z a] -> z b)
liftL f ls = lift1 f (zListToListZ ls)

-- relational and boolean operators
(>*),  (<*)  :: (Ord a, Zippable z) => z a -> z a -> z Bool
(<=*), (>=*) :: (Ord a, Zippable z) => z a -> z a -> z Bool
(&&*), (||*) :: Zippable z => z Bool -> z Bool -> z Bool
(==*), (/=*) :: (Eq a, Zippable z) => z a -> z a -> z Bool

-- the Maybe type
nothingZ     :: Zippable z => z (Maybe a)
justZ        :: Zippable z => z a -> z (Maybe a)
maybeZ       :: Zippable z => z a -> z (b -> a) -> z (Maybe b) -> z a

-- the Show class
showZ        :: (Show a, Zippable z) => z a -> z String

-- String
(++*)        :: Zippable z => z String -> z String -> z String

-- Boolean
trueZ       :: Zippable z => z Bool
falseZ      :: Zippable z => z Bool
notZ        :: Zippable z => z Bool -> z Bool
ifZ         :: Zippable z => z Bool -> z a -> z a -> z a

5.4  Behaviors

These primitives behaviors relate to the current time:
timeB       :: Behavior i Time  -- the global time
ltimeB      :: Behavior i Time  -- the local time
The global time at the start of the FRP program is always 0. The local time starts from 0 when the current behavior or event begins.

constB turns a static value into a behavior:
constB      :: a -> Behavior i a
constB      =  lift0

A behavior transformer changes the value of a behavior:
unzipB      :: Behavior i (a,b) -> (Behavior i a, Behavior i b)
withTimeB   :: Behavior i a -> Behavior i (a, Time)

These functions shape behaviors in response to events:
switch    :: GEvent e => Behavior i a -> e i (Behavior i a) -> Behavior i a
iSwitch   :: GEvent e => Behavior i a -> e i (Behavior i a) -> Behavior i a

till      :: GEvent e => Behavior i a -> e i (Behavior i a) -> Behavior i a
a `till` e = a `switch` onceE e
iTill     :: GEvent e => Behavior i a -> e i (Behavior i a) -> Behavior i a
a `iTill` e = a `iSwitch` onceE e

switchAccumB   :: GEvent e =>
                  Behavior i a -> e i (Behavior i (a -> a)) -> Behavior i a
iSwitchAccumB  :: GEvent e =>
                  Behavior i a -> e i (Behavior i (a -> a)) -> Behavior i a

stepB          :: GEvent e => a -> e i a -> Behavior i a
x0 `stepB` e   =  constB x0 `switch` e ==> constB
iStepB         :: GEvent e => a -> e i a -> Behavior i a
x0 `iStepB` e  =  constB x0 `iSwitch` e ==> constB

stepAccumB     :: GEvent e => a -> e i (a->a) -> Behavior i a
iStepAccumB    :: GEvent e => a -> e i (a->a) -> Behavior i a

You might have noticed each of the combinators in this group comes in two flavors, once with the "i" prefix and one without. The difference is rather subtle. Usually you only need to use the one without the "i" prefix.

These functions return the integral or derivative of a behavior. The VectorSpace class is defined in 6.1.1, and includes the type FRPReal, Vector2 and Vector3.
integralB       :: VectorSpace v => Behavior i v -> Behavior i v
derivativeB     :: VectorSpace v => Behavior i v -> Behavior i v

5.5  Events

The type Event i a denotes a series of event occurances, each producing a value of type a. The clocked event type
data Clock c i => CEvent c i a = ...
is the same as Event i a, except that it's associated with a clock type, which uniquely determines the occurrence pattern of all events of this clock type. The important thing to remember is that same clock type means same occurrence pattern.

In this section, we examine event stream operations. You will notice that some functions work with both Event and CEvent (those with context GEvent e => ...) and some only work with Event. That's because not all operations preserve the occurrence pattern of an event.

These are the basic event generators:
neverE         :: Event i a
constE         :: Time -> a -> Event i a
alarmE         :: Time -> DeltaTime -> Event i ()
timeIsE        :: Time -> Event i ()
timeIsE t      =  constE t ()

The neverE event is the never occuring event. The constE function returns an event stream with a single event at the specified time and with the given value. The alarmE function defines a regularly occuring event.

Continuous behaviors may be converted into events by the following functions:
whenE         :: Behavior i Bool -> Event i ()
isTrueE       :: Behavior i Bool -> Event i ()

Both events occur when the behavior changes from False to True. If the behavior is True from the beginning, then whenE does not have an occurrence while isTrueE does. This is best illustrated by the example:
e1 = whenE (timeB >=* 1)    -- occurs when the gloable time exceeds 1,
                            -- but not if the start time of e1 is
                            -- >= 1.
e2 = isTrueE (timeB >=* 1)  -- occurs when the gloable time exceeds 1,
                            -- or when e2 is started if the start time
                            -- is >= 1.

Events obtained using these functions are called predicate events.

A snapshot captures the value of a continuous behavior at the time of an event occurance.
snapshotE   :: GEvent e => e i a -> Behavior i b -> e i (a, b)
snapshotE_  :: GEvent e => e i a -> Behavior i b -> e i b
e `snapshotE_` b = e `snapshotE` b ==> snd

An event transformer changes the value associated with an event.
(==>)        :: Event i a -> (a -> b) -> Event i b
(-=>)        :: Event i a -> b -> Event i b
e -=> x      = e ==> const x

tagE         :: GEvent e => e i a -> [b] -> e i (a,b)
tagE_        :: GEvent e => e i a -> [b] -> e i b
e `tagE_` bs = (e `tagE` bs) ==> snd

withTimeE    :: GEvent e => e i a -> e i (a, Time)
withTimeE e  = e `snapshotE` timeB
timeOfE      :: GEvent e => e i a -> e i Time
timeOfE e    = e `snapshotE_` timeB

unzipE       :: GEvent e => e i (a,b) -> (e i a, e i b)
unzipE e     = (e ==> fst, e ==> snd)

The tagE functions pair event occurrences with elements of a list. The list must have enough elements to match the occurrences, or a run-time error will be generated. The withPrevE functions saves the value generated by the prior occurance and pairs it with the current value.

As with the behavioral form of switchers, these functions also switch (clocked) event streams:
switch    :: GEvent e => Event i a -> e i (Event i a) -> Event i a
iSwitch   :: GEvent e => Event i a -> e i (Event i a) -> Event i a
till      :: GEvent e => Event i a -> e i (Event i a) -> Event i a
iTill     :: GEvent e => Event i a -> e i (Event i a) -> Event i a

switch    :: GEvent e => CEvent c i a -> e i (CEvent c i a) -> CEvent c i a
till      :: GEvent e => CEvent c i a -> e i (CEvent c i a) -> CEvent c i a

These functions filter event streams, possibly removing occurances. Some of these also alter the values in the event stream.
filterE    :: GEvent e => (a -> Bool) -> e i a -> Event i a
filterByE  :: GEvent e => (a -> Maybe b) -> e i a -> Event i b
onceE      :: GEvent e => e i a -> Event i a

These functions combine event streams.
mergeE      :: (GEvent e1, GEvent e2) =>
               (a -> a -> a) -> e1 i a -> e2 i a -> Event i a
(.|.)       :: (GEvent e1, GEvent e2) => e1 i a -> e2 i a -> Event i a
mergeCE     :: (a -> a -> a) ->
                CEvent c1 i a -> CEvent c2 i a -> CEvent (Either c1 c2) i a
andThenE    :: (GEvent e1, GEvent e2) =>
                e1 i a -> (a -> e2 i b) -> Event i b
andThenE_   :: (GEvent e1, GEvent e2) =>
                e1 i a -> e2 i b -> Event i b
e1 `andThenE_` e2 = e1 `andThenE` const e2
bothE       :: (GEvent e1, GEvent e2) =>
                e1 i a -> e2 i b -> Event i (a,b)

The .|. operator merges two event streams; if both streams generate an event at the same time either value may be chosen. The andThen_ function returns events in the second stream that are after the initial event in the first stream. For example,
atPlace p1 `andThen_` atPlace p2
happens when first atPlace p1 happens followed by atPlace p2. bothE is similar, but occurs once only when both arguments have occurred.

These are "stream-based" event functions:
delayE     :: a -> Event i a -> Event i a
delay1E    :: GEvent e => e i a -> e i a
delayCE    :: Clock c i => a -> CEvent c i a -> CEvent c i a
newHeadE   :: GEvent e => a -> e i a -> e i a
scanE      :: GEvent e => (a -> b -> a) -> a -> e i b -> e i a
withPrevE  :: GEvent e => a -> e i a -> e i (a,a)
accumE     :: GEvent e => a -> e i (a -> a) -> e i a

Here are something special to clocked events:
toEvent    :: GEvent e => e i a -> Event i a

clockE     :: Clock c i => CEvent c i ()
clockOfE   :: Clock c i => CEvent c i a -> CEvent c i ()
clockOfE _ =  clockE

toEvent converts a clocked event to an ordinary event. clockE and clockOfE return the clock associated with a clock type and that of a clocked event, respectively. The Clock c i => ... context ensures that the clock type c does have a clock.

5.6  Running Signals

In
a = b `till` c -=> d
the behavior d only becomes alive when c occurs, so d does not see anything that happened before c occurs. Now suppose d has memory on the history (e.g. d is the odometer of a robot, and is defined as the integral of the robot velocity), then we may want d to start together with b (such that the odometer is not reset to 0 with c). This is achieved by making d a running behavior:
runningIn :: Behavior i a -> (Behavior i a -> Behavior i b) ->
             Behavior i b

a = d `runningIn` \d' ->
      b `till` c -=> d'

The code reads: d is started (running) side by side with b `till` c -=> d', and is bound to variable d', such that when c occurs, we switch into a behavior that had been started earlier.

Perhaps it's easier to understand
foo `runningIn` \b -> bar
as syntactic sugar for
letrun b = foo in bar
We can not really use the letrun syntax though, because Haskell does not allow us to define constructs that introduce binding.

runningIn also works with events and clocked events. In fact, its exact type is:
runningIn :: RunningIn a b i => a -> (a -> b i x) -> b i x

instance RunningIn (Behavior i a) Behavior i
instance RunningIn (Behavior i a) Event i
instance RunningIn (Behavior i a) (CEvent c) i
instance RunningIn (Event i a) Behavior i
instance RunningIn (Event i a) Event i
instance RunningIn (Event i e) (CEvent c) i

The first argument of runningIn can also be tuples or lists:
instance (RunningIn a x i, RunningIn b x i) =>
          RunningIn (a,b) x i
instance (RunningIn a x i, RunningIn b x i, RunningIn c x i) =>
          RunningIn (a,b,c) x i
instance (RunningIn a x i, RunningIn b x i,
          RunningIn c x i, RunningIn d x i) =>
          RunningIn (a,b,c,d) x i
instance RunningIn a x i => RunningIn [a] x i

This means we can rewrite
b `runningIn` \b' ->
    d `runningIn` \d' ->
        e `runningIn` \e' ->
            ...
to the more concise version:
(b,d,e) `runningIn` \(b',d',e') ->
    ...

5.7  Tasks

We have seen how to make a behavior reactive to some event:
a = b `till` c -=> d
Here, a behaves as b until c occurs, then it behaves as d. This is known as Continuation Passing Style.

Sometimes we would like to express "be like this until foo happens" without specifying what to be next. This concept is called task. A task combines a behavior and a terminating event, and can be composed either sequentially or in parallel. By wise use of tasks, we can achieve very structured code.

Suppose we are to write a robot controller behavior which does the following:

Go forward until stuck, then back up a little bit, make a left turn, and repeat the action sequence.

In the continuation-passing style, we can code this in a monolithic piece without using tasks, but the code will be much more modular and reusable if we divide the work into several small tasks:

  1. Go forward until stuck;
  2. Back up a little bit;
  3. Make a left turn.

We can define these small tasks independently, and then compose them in the way we want. When you write a large FRP program, it is recommended to use tasks as your basic building blocks.

5.7.1  Task Basics

Task is a very generic concept. We can imagine many different types of tasks: some may carry an internal state, some may have access to an environment, some may be able to send messages, and etc. Naturally, these different types of tasks may require different implementation. Hence, instead of having a single Task data type, we define a GTask (generic task) class, and then provide a bounch of instances of that class (remember that a class in Haskell is just an interface, and instances of a class are types or type constructors).

The mkTask function is the basic building block for tasks. The liftT function creates a task without an exit event.
mkTask      :: GTask t => Behavior i b -> Event i x -> t i b x
liftT       :: GTask t => Behavior i b -> t i b x
liftT b     =  mkTask b neverE
We can see that in a task type t i b x, b is the behavior result type, x is the event result type, and i is the input type for both the behavior and the event.

Tasks are sequenced using >>= or >>:
(>>=)          :: GTask t => t i a b -> (a -> t i a c) -> t i a c
(>>)           :: GTask t => t i a b -> t i a c -> t i a c
t1 >> t2       =  t1 >>= (const t2)

However, these operations are more commonly written out using do notation:
  do r <- t1;
     t2
is the same as t1 >>= (\r -> t2). The indentation here is important -- the r and t2 must be in the same column (unless explicit braces are used after the do). Similarly,
   do t1
      t2
is the same as t1 >> t2. Of course, do notation allows more than two tasks to be written in sequence.

The return function defines an empty task:
return      :: GTask t => b -> t i a b
The task defined by return immediately exits with the specified value.

Recursion and case statements can also be mixed in, allowing arbitrarily complex task sequencing structures, such as this one to repeat a task until it returns False:
repeatT :: GTask t => t i a Bool -> t i a ()
repeatT t = do r <- t
               if r then repeatT t
                    else return ()

This function repeats a task forever:
foreverT ::  GTask t => t i a e -> t i a e'
foreverT t = ft where ft = t >> ft

The bmapT function modifies the behavior in a task:
bmapT   :: GTask t => (Behavior i a -> Behavior i b) ->
                      t i a x -> t i b x

The fmap function modifies the result of a task:
fmap          :: GTask t => (x -> y) -> t i b x -> t i b y
fmap f t       =  do r <- r; return (f r)

This function runs a behavior in parallel with a task, adding a snapshot of the behavior value at the end of the task to the task result.
snapshotT     :: GTask t => Behavior i a -> t i b x -> t i b (x, a)

Sometimes we want to sample a behavior and return immediately. snapshotNowT does this for us:
snapshotNowT :: GTask t => Behavior i b -> t i a b

5.7.2  Subclasses of GTask

It does not always make sense, or is possible, to interrupt a task. Hence we have a subclass of GTask for those tasks capable of being interruptted. This subclass is named InterruptTask.

The following function changes the termination event of a task. The overall task ends when either the old or the new termination event occurs. The new event is parameterized over the behavior in the task. This is usually used to add termination to an non-terminating task, but can also be used to interrupt a terminating task.
tillT    :: InterruptTask t => t i b x ->
                               (Behavior i b -> Event i x) ->
                               t i b x

When the new termination event does not depend on the behavior in the task, we prefer to use tillT_:
tillT_   :: InterruptTask t => t i b x -> Event i x -> t i b x
t `tillT_` e = t `tillT` const e

We can limit the time a task can run:
timeLimitT  :: InterruptTask t => DeltaTime -> t i a e -> t i a (Maybe e)

timeLimitT_ :: InterruptTask t => DeltaTime -> t i a e -> t i a ()
timeLimitT_ dt t = timeLimitT dt t >> return ()

Consider a robot with more than one control, for example both wheel controls and camera controls. If we have tasks for each individual control, how can we combine these tasks into a single controlling task? Note that in the behavioral world, this problem is trivial: behaviors run naturally in parallel. For tasks, though, such a parallel combination becomes trickier. If we combine two tasks, when does the combined task terminate? When the first subtask terminates? What value is returned by the combined task? We resolve these difficulties by terminating the overall task when either subtask terminates, returning the value of the event occurring first. Same as for tillT, we expect (|||) to work only for a subclass of GTask:
(|||)     :: ParallelTask t =>  t i a x -> t i b y -> t i (a, b) (Either x y)

Here Either is a predefined type:
data Either a b = Left a | Right b

t1 ||| t2 returns Left x if t1 ends first with value x, or Right y if t2 ends first with y.

Some task types support message sending. This functionality is captured by the MsgTask subclass. This class has one method for sending messages:
class GTask t => MsgTask t m where
    sendT :: m -> t i b ()
sendT
sends the message and terminates immediately.

Some tasks have mutable internal states, which can be accessed by:
getState  ::  t i b s
setState  ::  (s -> s) -> t i b s

getState is an instantaneous task that returns the current state. setState applies a state update function to the current state, and returns the state before the update.

5.7.3  Examples Using Tasks

The examples in this section apply to Frob. In Frob, the robot controller is represented as a behavior:
type RController i o = Behavior i (o -> o)
The values yielded by the behavior are functions that update the command being sent to the robot.

By packaging an RController with a terminating event, we get a RobotTask:
type RobotTask s i o e = ...
RobotTask
is an instance of InterruptTask, ParallelTask and MsgTask, and is stateful. The s in the RobotTask is the internal state of the task, and e is the terminating event type.

The simplest tasks are infinite behaviors: these never terminate. The liftT function turns a behavior into a task. For example, given a differential drive robot, for which we can control the wheel velocity and the turning rate, a task to stop the robot can be defined as follows:
stopT   :: WheelControl o => RobotTask s i o e
stopT   =  liftT $ setWheelSpeeds (lift0 (0,0))

Generally, tasks will eventually terminate. The mkTask function couples a behavior with a terminating event. This function creates a task that proceeds forward until stuck:
forwardUntilStuckT :: (StuckDetection i, WheelControl o) => RobotTask s i o ()
forwardUntilStuckT = mkTask
                        (setWheelSpeeds $ lift0 (1,1))
                        stuck

The primitive robot task noCommandT is an "empty" task that never sends any command to the robot:
noCommandT :: RobotTask s i o e
noCommandT
does not terminate.

This task does nothing until the robot gets stuck, then it sends out a message:
stuckOuchT :: (StuckDetection i) => RobotTask s i o ()
stuckOuchT = do noCommandT `tillT_` stuck
                sendT "Ouch!\n"

Since stuckOuchT never sends any command to the robot, the robot will not move and therefore will not get stuck. Hence stuckOuchT by itself is not very useful. However we can run it in parallel with other tasks that do send out commands:
forwardT :: WheelControl o => Behavior i Speed -> RobotTask s i o a
forwardT v = liftT $ setWheelSpeeds (pairZ v v)

forwardOuchT :: (StuckDetection i, WheelControl o) => RobotTask s i o ()
forwardOuchT = forwardT 1 |.| stuckOuchT

Here |.| is an easy way to construct parallel robot task:
(|.|) :: RobotTask s i o e -> RobotTask s i o e -> RobotTask s i o e

forwardOuchT consists of two sub-tasks: forwardT 1 goes forward at 1 meter/second, and stuckOuchT says "Ouch!" and terminates when the robot hits something. forwardOuchT does the two at the same time.

This task goes forward until stuck, and then returns the distance travelled:
import qualified GeometryStatic as S

measureDistanceT :: (StuckDetection i, Odometry i, WheelControl o)
                 => RobotTask s i o Length
measureDistanceT = do p0 <- snapshotNowT position  -- where I started
                      forwardUntilStuckT           -- proceed until stuck
                      p  <- snapshotNowT position  -- where I am now
                      let d = S.magnitude (p S..-. p0)
                                                   -- calculate the distance
                      return d                     -- and return it

RobotTask can have an internal state, but so far we haven't used it. The state can be of any type you choose. To read the current state, use getState. To change it, use setState. Here is an example which counts the number of times the robot hits something:
hitCountT :: (StuckDetection i) => RobotTask Int i o e
hitCountT = foreverT $ do stuckOuchT  -- when stuck, say "Ouch!"
                          setState (+(1 :: Int))
                                      -- increment the counter
                          (count :: Int) <- getState
                          sendT $ "Can't believe it!  I've been hit "
                                  ++ show count
                                  ++ " time(s) today!\n"
                                      -- complain about bad luck
You might have noticed the use of explicit type signatures in getState and setState. That's because Haskell can not decide the type of these expressions in this case.

5.8  Debugging

We can watch behaviors or (clocked) events:
watchB       :: (Time -> a -> String) -> Behavior i a -> Behavior i a
watchE       :: GEvent e => (Time -> a -> String) -> e i a -> e i a

Whenever the behavior's value is updated, or the event has an occurrence, that value will be displayed using the function we supply to watchB or watchE. Examples:
distanceB :: Event Sensors FRPReal
bumperE   :: Event Sensors ()

... watchB (\t a -> show a ++ "\t") distanceB ...       -- ignore the time
... watchE (\t a -> "Bump at time " ++ show t ++ "!\t") bumperE ...

To debug tasks, we have the sayT function, which defines a task that prints to the console and immediately exits.
sayT     :: String -> Task i a ()
sayT s   = trace s $ return ()

5.9  Advanced Features

Features described in this section are generally intended for power users, and can be dangerous (e.g. degrading performance or screwing up heap) falling into the wrong hands. However, sometimes they are very useful. Proceed with caution.

5.9.1  Memoization


memoB     :: Behavior i a -> Behavior i a
memoE     :: Event    i a -> Event    i a

5.9.2  Coercion


behaviorToEvent    :: Behavior i (Maybe a) -> Event i a
eventToBehavior    :: Event i a -> Behavior i (Maybe a)
withBackgroundB    :: GEvent e => a -> e i a -> Behavior i a

5.9.3  Composition

Functions in this group allow us to compose signals as input-output transformers.

The (~>>) (read "compose") operator feeds the output of its first argument to the second argument. (~>>) is overloaded:
(~>>)     :: Behavior a b -> Behavior b c -> Behavior a c
(~>>)     :: Behavior a b -> Event    b c -> Event    a c

comapB and comapE change the input type of signals:
comapB    :: (a -> b) -> Behavior b c -> Behavior a c
comapE    :: (a -> b) -> Event    b c -> Event    a c

inputB and inputE allow us to observe the input of a signal directly:
inputB    :: Behavior i i
inputE    :: Event (Maybe i) i

6  FRP Libraries

6.1  Geometry

Since geometry is essential in many reactive systems (e.g. animation and robotics), FRP provides a geometry library for basic 2D and 3D geometrical operations. All of the operations described here are lifted, unless otherwise noted. The unlifted operations can be obtained from the static types module (GeometryStatic), conventionally imported using
import qualified GeometryStatic as S
allowing unlifted operations to be accessed using a S. prefix, as in S.point2XY. Some operations (e.g. point2XYCoordsTuple) only have lifted version, and they are noted in the comments.

A number of simple naming conventions are used. In operators, vector operands are indicated by the ^ symbol and point operands by the . symbol. Thus, .-. is an operation to subtract two points. Similarly, .+^ adds a point and a vector. When we can not overload an operator for both 2D and 3D operations, the # symbol is suffixed for the 3D operation, as in (.-.) and (.-.#).

The Num class handles some operations: along with ^+^, we can use an ordinary + for vector addition. Num is defined in Haskell Prelude, so please refer to the Haskell 98 Report for a complete list of operations in the class.

Transformations are represented as a distinct datatype and are applied to objects using %$ (for 2D) or %$# (for 3D). Composite transformation are created by combining basic transformation functions with compose2 or compose3.

6.1.1  2D Operations


-- 2D Vectors
xVector2, yVector2        :: Zippable z => z Vector2  -- unit vectors
vector2XY                 :: Zippable z => z FRPReal -> z FRPReal -> z
Vector2
Vector2XY                 :: pattern constructor      -- static version only
vector2Polar              :: Zippable z => z FRPReal -> z Angle -> z Vector2
vector2XYCoords           :: Zippable z => z Vector2 -> z (FRPReal, FRPReal)
vector2XYCoordsTuple      :: Zippable z => z Vector2 -> (z FRPReal, z
FRPReal)
                                                      -- lifted version only
vector2XCoord             :: Zippable z => z Vector2 -> z FRPReal
vector2YCoord             :: Zippable z => z Vector2 -> z FRPReal
vector2PolarCoords        :: Zippable z => z Vector2 -> z (FRPReal, Angle)
vector2PolarCoordsTuple   :: Zippable z => z Vector2 -> (z FRPReal, z Angle)
                                                      -- lifted version only
rotateVector2             :: Zippable z => z Angle -> z Vector2 -> z Vector2

instance Num Vector2                       -- fromInteger, * not allowed
instance Zippable z => Num (z Vector2)     -- fromInteger, * not allowed

-- 2D Points
origin2                   :: Zippable z => z Point2
point2XY                  :: Zippable z => z FRPReal -> z FRPReal -> z
Point2
Point2XY                  :: pattern constructor    -- static version only
point2Polar               :: Zippable z => z FRPReal -> z Angle -> z Point2
point2XYCoords            :: Zippable z => z Point2 -> z (FRPReal, FRPReal)
point2XYCoordsTuple       :: Zippable z => z Point2 -> (z FRPReal, z
FRPReal)
                                                      -- lifted version only
point2XCoord              :: Zippable z => z Point2 -> z FRPReal
point2YCoord              :: Zippable z => z Point2 -> z FRPReal
point2PolarCoords         :: Zippable z => z Point2 -> z (FRPReal, Angle)
point2PolarCoordsTuple    :: Zippable z => z Point2 -> (z FRPReal, z Angle)
                                                      -- lifted version only
distance2                 :: Zippable z => z Point2 -> z Point2 -> z FRPReal
distance2Squared          :: Zippable z => z Point2 -> z Point2 -> z FRPReal
linearInterpolate2        :: Zippable z => z Point2 -> z Point2 -> z FRPReal
                                        -> z Point2
point2ToVector2           :: Zippable z => z Point2 -> z Vector2
vector2ToPoint2           :: Zippable z => z Vector2 -> z Point2
(.+^), (.-^)              :: Zippable z => z Point2 -> z Vector2 -> z Point2
(.-.)                     :: Zippable z => z Point2 -> z Point2 -> z Vector2

-- VectorSpace class
zeroVector       :: (Zippable z, VectorSpace v) => z v
(*^)             :: (Zippable z, VectorSpace v) => z FRPReal -> z v -> z v
(^/)             :: (Zippable z, VectorSpace v) => z v -> z FRPReal -> v
(^+^),(^-^)      :: (Zippable z, VectorSpace v) => z v -> z v -> z v
dot              :: (Zippable z, VectorSpace v) => z v -> z v -> z FRPReal
magnitude        :: (Zippable z, VectorSpace v) => z v -> z FRPReal
magnitudeSquared :: (Zippable z, VectorSpace v) => z v -> z FRPReal
normalize        :: (Zippable z, VectorSpace v) => z v -> z v

-- 1D Vector Space
instance VectorSpace FRPReal
-- 2D Vector Space
instance VectorSpace Vector2

-- 2D Transformations
mkTransform2     :: Zippable z => z FRPReal -> z FRPReal -> z FRPReal
                               -> z FRPReal -> z FRPReal -> z FRPReal
                               -> z Transform2
transform2Matrix :: Zippable z => z Transform2 
                               -> z (FRPReal,FRPReal,FRPReal,
                                     FRPReal,FRPReal,FRPReal)
identity2        :: Zippable z => z Transform2
translate2       :: Zippable z => z Vector2 -> z Transform2
rotate2          :: Zippable z => z Angle -> z Transform2
compose2         :: Zippable z => z Transform2 -> z Transform2
                                               -> z Transform2
scaleXY2         :: Zippable z => z FRPReal -> z FRPReal -> z Transform2
uscale2          :: Zippable z => z FRPReal -> z Transform2
mirrorX2         :: Zippable z => z Transform2
mirrorY2         :: Zippable z => z Transform2
mirrorXY2        :: Zippable z => z Transform2
inverse2         :: (Zippable z, Monad m) => z Transform2 -> z (m Transform2)

class Tranformable2 a where
   (%$)    :: Transform2 -> a -> a  -- Applies a transform

instance Transformable2 Point2
instance Transformable2 Vector2

class Tranformable2Z a where
   (%$)    :: Zippable z => z Transform2 -> z a -> z a  -- Applies a
transform

instance Transformable2 a => Transformable2Z a

Note that the operator (%$) appears in both Transformable2 and Transformable2Z. Usually this causes a name clash, but not here since the static version is in the GeometryStatic module and imported qualified.

6.1.2  3D Operations


-- 3D Vectors
xVector3                  :: Zippable z => z Vector3   -- unit vector
yVector3                  :: Zippable z => z Vector3   -- unit vector
zVector3                  :: Zippable z => z Vector3   -- unit vector
vector3XYZ                :: Zippable z => z FRPReal -> z FRPReal ->
                                           z FRPReal -> z Vector3
Vector3XYZ                :: pattern constructor       -- static version
only
vector3Spherical          :: Zippable z => z FRPReal -> z Angle ->
                                           z Angle -> z Vector3
vector3XYZCoords          :: Zippable z => z Vector3 ->
                                           z (FRPReal, FRPReal, FRPReal)
vector3XYZCoordsTuple     :: Zippable z => z Vector3 ->
                                           (z FRPReal, z FRPReal, z FRPReal)
                                                       -- lifted version
only
vector3XCoord             :: Zippable z => z Vector3 -> z FRPReal
vector3YCoord             :: Zippable z => z Vector3 -> z FRPReal
vector3ZCoord             :: Zippable z => z Vector3 -> z FRPReal
vector3SphericalCoords    :: Zippable z => z Vector3 ->
                                           z (FRPReal, Angle, Angle)
vector3SphericalCoordsTuple :: Zippable z => z Vector3 ->
                                             (z FRPReal, z Angle, z Angle)
                                                       -- lifted version
only
instance Num Vector3                       -- fromInteger, * not allowed
instance Zippable z => Num (z Vector3)     -- fromInteger, * not allowed

-- 3D Points
origin3                   :: Zippable z => z Point3
point3XYZ                 :: Zippable z => z FRPReal -> z FRPReal ->
                                           z FRPReal -> z Point3
Point3XYZ                 :: pattern constructor      -- static version only
point3Spherical           :: Zippable z => z FRPReal -> z Angle -> z Angle
->
                                           z Point3
point3XYZCoords           :: Zippable z => z Point3 ->
                                           z (FRPReal, FRPReal, FRPReal)
point3XYZCoordsTuple      :: Zippable z => z Point3 ->
                                           (z FRPReal, z FRPReal, z FRPReal)
                                                       -- lifted version
only
point3XCoord              :: Zippable z => z Point3 -> z FRPReal
point3YCoord              :: Zippable z => z Point3 -> z FRPReal
point3ZCoord              :: Zippable z => z Point3 -> z FRPReal
point3SphericalCoords     :: Zippable z => z Point3 ->
                                           z (FRPReal, Angle, Angle)
point3SphericalCoordsTuple:: Zippable z => z Point3 ->
                                           (z FRPReal, z Angle, z Angle)
                                                       -- lifted version
only
distance3                 :: Zippable z => z Point3 -> z Point3 -> z FRPReal
distance3Squared          :: Zippable z => z Point3 -> z Point3 -> z FRPReal
linearInterpolate3        :: Zippable z => z Point3 -> z Point3 -> z FRPReal
->
                                           z Point3
point3ToVector3           :: Zippable z => z Point3 -> z Vector3
vector3ToPoint3           :: Zippable z => z Vector3 -> z Point3
mirrorX3                  :: Zippable z => z Point3 -> z Point3
mirrorY3                  :: Zippable z => z Point3 -> z Point3
mirrorZ3                  :: Zippable z => z Point3 -> z Point3
mirrorXY3                 :: Zippable z => z Point3 -> z Point3
mirrorXZ3                 :: Zippable z => z Point3 -> z Point3
mirrorYZ3                 :: Zippable z => z Point3 -> z Point3
mirrorXYZ3                :: Zippable z => z Point3 -> z Point3
(.+^#), (.-^#)            :: Zippable z => z Point3 -> z Vector3 -> z Point3
(.-.#)                    :: Zippable z => z Point3 -> z Point3 -> z Vector3

-- 3D Vector Spaces
instance VectorSpace Vector3

Please refer to section 6.1.1 for the definition of the VectorSpace class.


-- 3D Transformations
identity3  :: Zippable z => z Transform3
translate3 :: Zippable z => z Vector3 -> z Transform3
rotate3    :: Zippable z => z Vector3 -> z FRPReal -> z Transform3
scale3     :: Zippable z => z Vector3 -> z Transform3
compose3   :: Zippable z => z Transform3 -> z Transform3 -> z Transform3
uscale3    :: Zippable z => z FRPReal -> z Transform3

class Transformable3 a where
  (%$#)  ::  Transform3 -> a -> a                      -- not currently used

class Tranformable3Z a where
  (%$#)  ::  Zippable z => z Transform3 -> z a -> z a  -- not currently used

instance Transformable3 a => Transformable3Z a

6.2  Graphics

The standard distribution of FRP includes a graphics module, which can be very useful for animation and debugging. To use graphics, import GraphicsFRP instead of FRP. The graphics operations are lifted. To access the static version, you can import the Graphics module qualified. The following example shows how:
module Follow where

import GraphicsFRP
import qualified GeometryStatic as S
import qualified Graphics as G

p1, p2 :: Behavior UserAction Point2

p1 = (point2XY 2 2) .+^ integralB (0.7 *^ (mouseB .-. p1))
p2 = origin2        .+^ integralB (0.5 *^ (p1     .-. p2))

world = pairZ p1 p2

showWorld (p1,p2) =
  S.moveTo p1 (G.withColor Red obj) `G.over`
  S.moveTo p2 (G.withColor Blue obj)
    where obj = S.stretch 0.3 G.circle

main = animate $ lift1 showWorld world

In this example, a red ball (p1) follows the mouse movement, and a blue ball (p2) follows the red ball at a slower speed. This example also shows how to work with points and vectors.

The graphics library contains the following drawing primitives:
data Color = Black | Blue | Green | Cyan | Red | Magenta | Yellow | White

data Picture = ...
instance Transformable2 Picture

black, blue, green, cyan, red, magenta, yellow, white
            :: Zippable z => z Color
over        :: Zippable z => z Picture -> z Picture -> z Picture
emptyPic    :: Zippable z => z Picture
line        :: Zippable z => z Point2 -> z Point2 -> z Picture
polygon     :: Zippable z => [z Point2] -> z Picture
polyline    :: Zippable z => [z Point2] -> z Picture
text        :: Zippable z => z String -> z Picture
arc         :: Zippable z => z Point2 -> z Point2 -> z Angle -> z Angle
                          -> z Picture
circle      :: Zippable z => z Picture
withColor   :: Zippable z => z Color -> z Picture -> z Picture
rect        :: Zippable z => z Point2 -> z Point2 -> z Picture
rectline    :: Zippable z => z Point2 -> z Point2 -> z Picture
square      :: Zippable z => z Picture
squareline  :: Zippable z => z Picture

The static version of these primitives can be obtained by importing Graphics qualified, as already shown in the Follow example.

To get user inputs, use the following primitives:
data UserAction = ... GUI user actions

keyE        :: Event UserAction Char       -- keyboard inputs
mmE         :: Event UserAction Point2     -- mouse moves
mouseB      :: Behavior UserAction Point2  -- current mouse position
lbpE        :: Event UserAction ()         -- left mouse button presses
rbpE        :: Event UserAction ()         -- right mouse button presses