Event handling has turned over a new leaf. Instead of “subscribing” to “publishers”, you create a “stream” of events. When the event stream is implemented like a collection, it becomes a powerful abstraction that lets you create complicated event handling logic with relatively little effort.
The approach is called “Functional Reactive Programming”, or FRP for short. It’s basically a new angle on the observer pattern. When you start thinking about the Publisher of events as a Collection of events, you can transform that collection in the way you would expect from a functional-able language like Scala (map
, filter
, etc), and react to events using foreach
. I’ll dive in with some examples “before” and “after” switching to FRP style.
//BEFORE
val pub = new Publisher[Int]
//AFTER
val events = new EventSource[Int]
Nothing special there.. how about listening to events?
//BEFORE
val sub = new Subscriber[Int] {
def receive(event: Int) = { /* handle event */ }
}
sub.subscribeTo(pub)
//AFTER
for(event <- events) { /* handle event */ }
Okay, that seems to be a bit more concise. Now how about these “combinators”?
//BEFORE
val pub2 = new Publisher[Int]
val pubAdaptor = new Subscriber[Int] {
def receive(event: Int) = {
pub2.publish(event * 2)
}
}
pubAdaptor.subscribeTo(pub)
val sub = new Subscriber[Int] {
def receive(event: Int) = { /* handle event */ }
}
sub.subscribeTo(pub)
//AFTER
for(event <- events.map(_ * 2)) { /* handle event */ }
Wow! That certainly saved a lot of space. In case it’s not obvious, pub2
is supposed to be a publisher that fires any event from pub
after multiplying it by 2. To get that, I had to set up an intermediate subscriber that would tell pub2
to fire the new event. In the “after” case, all I had to do was call map(_ * 2)
on events
, and all of that wiring was taken care of for me.
Assuming that the EventSource[T]
class has all of the same combinators that you’d find on any other scala collection, you could presumably do something like…
val transformedEvents = events.takeWhile(_ != 5).filter(_ % 2 == 0).map(List(_ / 2))
val someOtherEvents = events2 collect {
case "hello" => List(1,2,3)
case "goodbye" => List(3,2,1)
}
val eitherEvent: transformedEvents.union(someOtherEvents)
There are a few things out there that already offer some of this functionality. C# has the “Reactive Extensions“ for LINQ, and there are a few open-source libraries floating around for scala like reactive-web. I took a stab at making my own implementation of this framework, which I call “Scala FRP.” It’s hosted on Github, and I’ve been making occasional additions to it since I got it to a stable state a couple weeks ago. The docs are pretty thorough, and you are free to look at the source code.
For further reading on the concept I described, Deprecating the Observer Pattern is where I first encountered it. The paper is a very difficult read, but is definitely worth at least looking at if you are interested in learning more.