
Research
Security News
The Growing Risk of Malicious Browser Extensions
Socket researchers uncover how browser extensions in trusted stores are used to hijack sessions, redirect traffic, and manipulate user behavior.
= 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 (http://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
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 update.
This 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 that lets 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 various things, one of which is to change 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 communicate changes to the time-varying value to 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 (c) 2012 Brian Marick. See LICENSE.txt for further details.
FAQs
Unknown package
We found that rubactive demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover how browser extensions in trusted stores are used to hijack sessions, redirect traffic, and manipulate user behavior.
Research
Security News
An in-depth analysis of credential stealers, crypto drainers, cryptojackers, and clipboard hijackers abusing open source package registries to compromise Web3 development environments.
Security News
pnpm 10.12.1 introduces a global virtual store for faster installs and new options for managing dependencies with version catalogs.