A sketch of reactive programming for Ruby
Ruby
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
test
.gitignore
.rvmrc
Gemfile
LICENSE.txt
README.rdoc
Rakefile
rubactive.gemspec

README.rdoc

Rubactive

I had a hard time figuring out what “functional reactive programming” and “reactive programming” meant from documentation on the web. Implementing it helped. This is the implementation. You can tell me if I'm still confused.

Note: a big part of my confusion was because, I believe, the terminology used obscures more than it reveals, so I tried to pick better names. Again, you'll be the judge of whether I succeeded.

The implementation I chose is most closely modeled after Flapjax (www.flapjax-lang.org/). Their tutorial is pretty good, though it uses the conventional names and is occasionally obfuscated by having flapjax code mixed up with HTML. Still: good job, guys!

Note: my implementation is extremely naive (for example, it doesn't even try to deal with “glitching”), and it's missing some of the nice utility functions Flapjax has.

Rubactive

To use Rubactive, you'll need Ruby 1.9. (I use 1.9.2.) Do this in irb:

require 'rubactive'
include Rubactive

The idea of reactive programming is that you have values that change as a reaction to changes in other values. There are two ways to look at such values:

time-varying values

There's a single value that changes over time. It might be 5 at one moment and 6 at another. That might happen because you explicitly changed it, but it might also change because some other time-varying value changed and this one reacted to that.

In Rubactive, such values are of class Rubactive::TimeVaryingValue.

streams of values

Instead of one value that changes, it can be convenient to think of a stream of distinct values, arriving one at a time. A stream might change because some code added a value to it, or because it reacted to a new value appearing on another stream.

In Rubactive, such streams are of class Rubactive::DiscreteValueStream.

I used terminology like “a way to look at” and “convenient to think of” because there's no huge difference between the two classes. They are both thin wrappers (that mainly provide different terminology) over a base ReactiveNode class (which I haven't bothered to document).

TimeVaryingValues

The simple way to create a time-varying value is to give it a starting value:

origin = TimeVaryingValue.starting_with(0)

The current value of origin can be found like this:

origin.current #=> 0

(Note: it's sort of lame that we refer to origin as a value but have to use current to get the… value… of the… value. Some reactive frameworks work to hide the fact that origin isn't really a value, but rather an object-containing-a-value. I don't do that.)

We can also create another time-varying value that will always be the same as origin:

exactly = TimeVaryingValue.follows(origin)
exactly.current #=> 0

Here's a way to see that exactly really does follow origin:

origin.change_to("dawn!")
exactly.current #=> "dawn!"

That's not wildly exciting, so let's have one time-varying value be a function of another:

upper = TimeVaryingValue.follows(origin) { | o | o.upcase }
upper.current #=> "DAWN!"
origin.change_to("dawn, paul, and sophie")
upper.current #=> "DAWN, PAUL, AND SOPHIE"

(As noted before, it'd be better if time-varying values looked like integers, or strings, or whatever, instead of objects containing integers, or strings, or whatever. As a gesture toward that, I made it so method_missing constructed new time-varying-values:

coolness = origin.upcase
coolness.current  # => "DAWN, PAUL, AND SOPHIE" 
origin.change_to("your name here")
coolness.current  # => "YOUR NAME HERE"

That really doesn't add anything to your understanding, but what's the point of programming in Ruby if you can't show off?)

There's no reason why time-varying values can't be dependent on more than one “origin”:

annoyance = TimeVaryingValue.starting_with(" [that's what she said]")
michael = coolness + annoyance
# above shorthand equivalent to:
#   michael = TimeVaryingValue.follows(coolness, annoyance) do | c, a |
#        c + a
#   end
michael.current #=> "YOUR NAME HERE [that's what she said]"

annoyance.change_to(" in bed!")
michael.current #=> "YOUR NAME HERE in bed!"

DiscreteValueStream

Now let's consider a stream of values, where a new value might appear at any instant. (You can probably see how this might be useful for modeling user input.) Here's how to create a stream that doesn't depend on anything:

values = DiscreteValueStream.manual

You can put something onto a stream and look at it:

values.add_value(5)
values.most_recent_value   #=> 5

As you might expect, you can have one stream follow another:

 boring_values = DiscreteValueStream.manual
 excited_values = DiscreteValueStream.follows(boring_values) do | b | 
     b.upcase + "!"
 end

boring_values.add_value("party")
excited_values.most_recent_value #=> "PARTY!"

The outside world

The “reactive world” is one in which values are tied together with relationships created by follows. But that reactive world is embedded within other code that's not reactive. For example, it might be that a change to a reactive value should make a user interface control change what it displays.

That can be done by handing a callback to the reactive value. Here's how a new addition to a value stream can affect the non-reactive world:

excited_values.on_addition do | most_recent |
   puts "This new value has been added: #{most_recent}"
end

boring_values.add_value("vegetate")
# This new value has been added: VEGETATE!

The same can be done with time-varying-values, but the method name is different (for clarity):

tvv = TimeVaryingValue.starting_with(8)
tvv.on_change do | current |
  puts "New value: #{current.inspect}"
end

tvv.change_to("Veterinarians >> human medicine people")
New value: "Veterinarians >> human medicine people"

An end-to-end-example

Consider a model-view-controller architecture that lets a user control a particular hardware setting. The user interface displays the current setting, and provides controls to let the user change it by some delta. In a typical MVC implementation, the controller takes an active role in shuttling events and values between layers of the system. But that responsibility could be implemented declaratively with reactive values.

Let's begin!

The current hardware setting is a time-varying value:

hardware_setting = TimeVaryingValue.starting_with(50)

The user's actions can be considered to be a stream of delta values:

deltas = DiscreteValueStream.manual

That stream of deltas should be combined with the hardware setting to produce a stream of desired settings:

user_changes = DiscreteValueStream.follows(deltas) do |delta|
    delta + hardware_setting.current
end

(Alternately, we could be more terse:

user_changes = deltas + hardware_setting.current

… but that would be showing off.)

Note: I'm not having the user_changes follow the hardware settings because independent changes to the hardware don't count as user changes.

When a user asks for a change, the hardware should be told to change. That steps out of the reactive framework, so it requires a callback. I'm going to pretend that the callback does lots of work to interact with the hardware and, if that work succeeds, changes the hardware_setting value:

user_changes.on_addition do |value|
  # The code talks to the real hardware and also sets the authoritative value:
  hardware_setting.change_to(value)
end

At this point, we've propagated the user's desires “downward” (toward the hardware), but we also have to propagate the truth about the hardware upward (toward the user interface). We could have the user interface directly reflect the low-level hardware_setting value, but lets decouple things a bit by having the displayed value follow the hardware_setting:

value_displayed = TimeVaryingValue.follows(hardware_setting)

(There'd presumably be some sort of on_change callback to put changed values into the user-interface control.)

So, now that we've done this wiring, how does it work?

The hardware setting starts at 50, because we told it that's the default:

hardware_setting.current #=> 50

Because the displayed value follows the hardware setting, it too is 50:

value_displayed.current #=> 50

Suppose the user clicks a button, enters a text value, or drags a slider—whatever. That provokes code that adds a value to the deltas stream. Let's simulate that:

deltas.add_value(5)

Did that count as a new user_change? Yes:

user_changes.most_recent_value  #=> 55

Did that (via the callback) change the value of the hardware setting? Yes:

hardware_setting.current #=> 55

Was that value reflected up to the user interface? Yes:

value_displayed.current #=> 55

All this was done declaratively (with a sort of DSL), rather than with writing controller methods. That's the promise of reactive programming.

Copyright

Copyright © 2012 Brian Marick. See LICENSE.txt for further details.