echo

A Functional Reactive Programming framework for Scala

1. Get It

You can download the latest jar of the framework here. Just stick it in your project folder. I'd personally recommend buildr for building and compiling Scala projects. If you're wanting to hack on the source you can get that here.

2. FRP

Functional Reactive Programming is concerned with programming with two new types: Behaviours and Events. Behaviours are continuous values that change over time and Events are infinite streams of values that 'occur' at a discrete point in time - we can think of them as as a stream of time and value pairs. Mental eh?

Lets see some examples. Here we're going to make a simple Behaviour:

  val beh = Behaviour(time => time * 2)

This Behaviour will, at a given time t have the value t * 2. So how do we actually get at that value? We use the eval function:

  val value = beh.eval()
  println(value)

The above code would evaluate the Behaviour at the current time and then print the calculated value out.

So what about Events? Well here's something. Imagine we have an Event mouseEv that occurs every time the left mouse button is pressed. We could construct a Behaviour like this:

  val false = Behaviour(time => false)
  val true = Behaviour(time => true)
  val beh = false.until(mouseEv, true)

This creates a Behaviour that has the value false until the left mouse button is pressed, at which point its value is true. Cool? How about this:

  val beh = false.toggle(mouseEv, true)

Guess what this does? Thats right. It produced a Behaviour that toggles between false and true whenever that crazy left mouse button is pressed.

Getting the hang of this? Lets jump in at the deep end:

   val mouseTimes = mouseEv.map((t, v) => t)
   val lastTime = Stepper(-1, mouseTimes)

Ok. Here we map the mouse button Event to return a new Event who's values are that of the times of each occurrence. We then use a Stepper. A Stepper is a Behaviour that has an initial value and an Event: at any point in time its value will be the Event's last occurrence's value (or the initial value if the Event hasn't occurred). With all that put together you will see that we've created a Behaviour that's value will always be the last time the mouse button was pressed (or -1 to specify it was never pressed).

That should all have hopefully got you into the right frame of mind. Lets move on to writing some code in echo...

3. Writing Echo Code

To write echo applications you simply extend the EchoApp class with your code's main object. This class already has a defined main function that takes care of some stuff for you so all you need to do is write your code in the setup function like so:

  object App extends EchoApp {
     def setup(args : Array[String]) {
       val beh = Behaviour(time => time * 2)
       val value = beh.eval()
       println(value)
     }
   }

Unfortunately in order to avoid the FRP semantics going bonkers you can only write echo code inside this setup function. This may seem restrictive but you'll find in FRP you generally write a small amount of code in an upfront declarative manner to define an entire applications run-time behaviour.

You might notice that a lot of the time the Behaviours we create are 'constant' (always have the same value). So we can express these easily we can just write constant Behaviours as their constant values:

 val beh = 5.until(event, 5)

4. Custom Events

You are not restricted to only using the Events provided in echo. You can simply extend the EventSource class to create your own:

  class CustomEvent extends EventSource[Int] {
    occur(5)
  }

This creates an Event that when instantiated occurs with a value of 5. The occur function used here is only available to classes that extends EventSource.

5. Adding Graphics

There is also a simple UI framework in echo so you make funky GUI apps for your users. What's cool about this is that you can set GUI components attributes to be Behaviours. Before we launch into all that though lets make a small little app with a button and some text:

  val frame = Frame(200, 200, List(
    Text("Hello you!"),
    Button("Click Me!")
  ))

This creates a Frame with a size of 200 x 200. It should be noted that these dimensions are Behaviours so if we set them to change over time the window size would stay up to date with the Behaviour's value. So lets do something with FRP:

  val button = Button("Click Me!")
  val text = "Hello".until(button.click, "Goodbye")

  val frame = Frame(200, 200, List(
    text,
    button
  ))

When this app runs the text component will say "Hello" but when a user hits the button it will change to saying "Goodbye". You can see pretty immediately that this allows us to build our GUI's in a much nicer way. No ActionEventListener here. Its hard to cover all the different components and attributes for the UI framework here but the docs should provide a good starting point.

6. Over the Interwebs...

Lets face it. These days its all about talking to other computers or connecting to the 'internet'. We build a couple of types to allow you to do this in Echo if you are that way inclined. These are Sender and Receiver. A Sender sends messages whenever a given Event occurs and a Receiver occurs whenever it receives a message. Lets build an example:

  val button = Button("Ping")
  val pinger = button.click.map((t, v) => "Ping")
  val sender = Sender("localhost", 1997, pinger)
  val receiver = Receiver(1997)

Here we create a Sender that will send the message "Ping" out to the specified IP and port number. You can see the Receiver uses the same port and as we are on the same computer the messages the messages will be received by the Receiver we created.

Of course we are now communicating over a network we've started writing FRP code that might fail due to something out of our control. Luckily we can deal with this. Remember Exceptions? Well we can the Sender and Receiver types have an errors Event that will occur whenever an Exception is thrown within their operation. So, if want a Behaviour that shows the last error message from our Sender:

  val error = Stepper("No errors!", sender.errors.map((t, e) => e.toString))

Using Events to deal with Exceptions should allow you to declaratively define how your code is going to deal with errors.

7. Going Deeper

If you're wanting to delve deeper into the possibilities reading the docs will be a good place to start. I'd also recommend checking out all the more academic explorations of FRP, most of which can be found here.