Hello Yampa

Having tried (more or less successfully) to find good introductory literature on the topic of Functional Reactive Programming (FRP) I decided to keep track of my own experiments and convert the resulting log to small tutorials. Take this as a disclaimer: I myself am trying to figure out how this all works. Any ideas how to do better are thus more than welcome.

Functional Reactive Programming is a technique to work with systems that change over time using the toolbox of functional programming. The idea is to treat the whole system as a “stream” which stems from some input, in between sits the programmer who is to decide how to use the input stream and create from it an output stream. Consider the following examples:

A GUI application

It is easily possible to interpret GUI applications as an instance of the reactive programming perspective:

  • The input signal shall be the user inputs. Consider the case in which the only means of input is a mouse with just one button, then the signal is a tuple (x,y,p) of the current coordinates x and y and a boolean p indication whether or not the mouse button is currently pressed.
  • The output signal is what is drawn on the screen. This might for example be a simple pixel array with RGB values for each point.
  • The task of the programmer is to couple the input signal to the output signal. As an example, we probably want to move the graphical cursor on the screen according to the current (x,y) position. This means that the corresponding pixels in the pixel array are changed accordingly.

https://danielmescheder.files.wordpress.com/2011/12/wpid-gui_application.png

A Robot Controller

A different use case is a controller of a robotic system. Imagine for example an arm with a number of joints to control. The fact that this is a system that exhibits some “behaviour over time” indicates that again FRP is applicable. And indeed:

  • The input signal will be the information we get from the sensors, measuring e.g. joint positions.
  • The output signal is whatever is sent to the robot actuators e.g. motor torques.
  • The task of the programmer is to map from the sensor data to the actuator signal.

https://danielmescheder.files.wordpress.com/2011/12/wpid-robot_controller1.png

FRP Frameworks

There are several Haskell frameworks that support FRP. Unfortunately I find it difficult to estimate their respective level of maturity and overall quality. I will be focusing on Yampa which can be installed quite easily using cabal:

$ cabal install Yampa

The Haskell Wiki list some more FRP frameworks but I found the documentation rather sparse.

The “Hello World” Example

Let’s start with a simple “Hello World” application. The idea is to achieve the following: The input to the system is static signal with value “Hello Yampa”. This signal will be piped through the main program without being changed. Thus, the resulting signal should be just “Hello Yampa”. All the output function has to do is to print it via putStrLn and indicate that it wants to terminate the program.

https://danielmescheder.files.wordpress.com/2011/12/wpid-hello_yampa.png

import FRP.Yampa

main :: IO ()
main = reactimate initialize input output process

initialize :: IO String
initialize  = return "Hello Yampa"

input :: Bool -> IO (DTime, Maybe String)
input _ = return (0.0, Nothing)

output :: Bool -> String -> IO Bool
output _  x = putStrLn x >> return True

process :: SF String String
process = identity

Let’s see bit by bit what happens here.

The type of the reactimate function, which serves as the core of the application is as follows:

reactimate
  :: IO a
     -> (Bool -> IO (DTime, Maybe a))
     -> (Bool -> b -> IO Bool)
     -> SF a b
     -> IO ()

Basically, the type argument a is the type of the input signal. In our case this will thus simply be a string. The first argument is the initial input data. As it is in the IO Monad, we could retrieve this data from the actual physical system (e.g. the position of the mouse cursor).

The second argument of reactimate is the function that supplies us with sensor data. I do not understand why it takes a Bool parameter and according to this page, that parameter is not even used. Again, it is wrapped in the IO Monad and we could use it to get the new position of the mouse cursor or the current sensor data. Let’s look at the type of the thing that is wrapped in IO: It is (DTime, Maybe a). The first part of the tuple is the amount of time that has passed since the last time the input function has been called. It is thus (in theory) our task to do the bookkeeping and to return the right value here. However, the way the Hello Yampa program works, input will actually never be called, thus the exact return value does not matter. The second element can be Nothing which translates to “there was no input”.

The return value of the output function signals whether or not we want to terminate: True stops the program. As in this simple example the return value is always True the program will terminate immediately after having executed output once.

More information about reactimate can be found here.

Timed “Hello World”

Obviously, printing “Hello Yampa” on a screen would not be the example were FRP would typically be applied. Instead, we want to deal with systems that change over time. To include this time element, let us consider a slightly extended “Hello World”-example in which

  • the input consists of a constant stream of “Hello Yampa” and the current time
  • the function is to filter all instances where the time is more than two seconds
  • the output is to print the result to console as before
import FRP.Yampa
import Data.IORef
import Data.Time.Clock

type Input = (String, DTime)
type Output = Maybe String

data Stopwatch = Stopwatch { zero :: UTCTime
                            prev :: UTCTime
                           }

startStopwatch :: UTCTime -> Stopwatch
startStopwatch now = Stopwatch now now

storeStopwatch :: IO (IORef Stopwatch)
storeStopwatch = getCurrentTime >>= (newIORef . startStopwatch)

diffTime :: (IORef Stopwatch) -> IO (DTime,DTime)
diffTime ref = do  
               now <- getCurrentTime
               (Stopwatch zero prev) <- readIORef ref
               writeIORef ref (Stopwatch zero now)
               let dt = realToFrac (diffUTCTime now prev)
                   timePassed = realToFrac (diffUTCTime now zero)

               return (dt, timePassed)

main :: IO ()
main = do
       handle <- storeStopwatch
       reactimate initialize (input handle) output process


initialize :: IO Input
initialize  = return ("Hello Yampa",0)

input :: (IORef Stopwatch) -> Bool -> IO (DTime, Maybe Input)
input ref _ = do
              (dt,timePassed) <- diffTime ref
              return (dt, Just ("Hello Yampa",timePassed))

output :: Bool -> Output -> IO Bool
output _ (Just x)  = putStrLn x >> return False
output _ Nothing = return True

process :: SF Input Output
process = arr afun
          where
            afun (message, timePassed)
              | timePassed <= 1 = Just message
              | otherwise       = Nothing

The first thing to notice here is the code that keeps track of the time that has passed. The start time and the time of the last call of input are stored in an IORef reference. I wonder why this kind of very generic looking code is not part of the reactimate loop.

The input function now returns a tuple containing the string and the time that has passed. Furthermore it updates the time of the stopwatch. The process function looks at the time that has passed and decides to either return Nothing or to just pipe through the message.

Using the arrow syntax for which we need

{-# LANGUAGE Arrows #-}

this can be written this in a different way:

process :: SF Input Output
process = proc (message, timePassed) -> returnA -< if (timePassed <= 1) 
                                                   then Just message 
                                                   else Nothing

I suppose that the arrow syntax does not really make sense in this simple case. Whenever the process function returns Nothing the program will stop.

In theory, the signal function is assumed to be continuous. Therefore we should get infinitely many instances of “Hello Yampa” printed to the screen. Obviously that’s not possible. And indeed, internally Yampa seems to discretize the signal function.

Conclusion

There are some things that puzzle me about Yampa. For instance:

  • There are those seemingly unused boolean parameters in the input and output functions.
  • I don’t quite understand why the whole time-bookkeeping work is not done internally.

Yet I think that these two examples provide some intuition about how a Yampa program is structured. Also it becomes apparent what the beauty of this approach is to a Haskell programmer: All unpure operations can be located in the input and output function. The process that translates input to output has no side effects at all. Now I have to look at some more sophisticated examples involving state.

Updates

  • 2011-12-26 Mon: Alfredo simultaniously worked on a kick-starter for the Reactive Banana library.
  • 2011-12-26 Mon: Gerold noted below that a value of Nothing in the input function actually corresponds to “there was no input”. I updated the text accordingly.

3 thoughts on “Hello Yampa

Add yours

  1. I like your loud thinking!

    > The second element can be Nothing which translates to “use the same input value as last time”.
    Nothing translates to ‘there was no input’ 🙂

    > There are those seemingly unused boolean parameters in the input and output functions.
    I think they were originially supposed to serve a purpose, but now are left as code artefacts. just ignore them or ask in Yampa mailing list.

    > I don’t quite understand why the whole time-bookkeeping work is not done internally.
    The bookkeeping actually is, I think you refer to the “elapsed time”. This is simply because you want to seperate the concern of “FRP infrastructure” and “how the time differences are produced”. You are simply using Data.Time.Clock, I always used a fixed amount of “60”, but we might as well use high-precision timer or external time givers.

    1. Thanks for your comments. I updated the text to include your remark that “Nothing translates to ‘there was no input'”.
      However, It seems that if there is no new input, Yampa will interpolate and use the last input as the new input to the signal function, right?

  2. I’m not quite sure what you mean by that.

    input :: … -> IO (DTime, Maybe Input)
    If you mean “DTime”, take a look at the integral SF. this is a very fundamental signal function which encapsulates time as a state. It uses Euler integration and if DTime would be 0, time wouldn’t progress (but I think this is prohibited). If you mean “Maybe Input”, than Nothing really is Nothing. You’re code has to provide handling of this case, so there cannot be anything to interpolate.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: