Message Based Reactive Programming


As I move more from working on Lispz as a language to using it as a support system for Empiric, I need to deal more with the asynchronous nature of the underlying (browser/JavaScript) architecture. Callbacks are efficient, but make for messy hard-to-follow code. Promises only help a little and are single-shot.

For the ATO we were looking at RxJS, reactive programming and by extension function reactive programming (frp). With care the approach creates clear declarative code. On the down side, RxJS in particular requires a big learning curve. Also after looking at a number of reactive libraries, I see that they have in common a messy, large and decidedly non-functional support tier.

Async/await is great for treating asynchronous sequences in a synchronous manner, but is only suitable for tightly coupled code. Also it is not universally available yet.

I want to do better for Lispz.

I had previously chosen messaging for decoupled communications between components. I was already using it in a reactive manner so that components can listen to user interactions. It was more of a pub/sub model producing code that was not as clear and declarative as I would like.

Time for some research. My first stop was The Reactive Manifesto. It states that reactive systems should be responsive, resilient, elastic and message driven. This confirmed my belief that extending my messaging paradigm to be more stream-like was the way to go. I also wanted to carefully consider and cater for responsiveness, resilience and elasticity once I fully understood the consequences.

At the core, message.send is used to send a single message to any listeners. Given that reactive programming is about data streams, we need need to send messages when ‘things’ happen in a stream. In the browser the most common stream comes from events in the DOM.

(ref @click (dom.click "my-message-address" document.body))
## is a specific case for
(ref @click (dom.message "click" "my-message-address" document.body))

The function returns the generated name of the queue. In this case it would be dom-click/my-message-address/.

The core of all reactive systems is to map the data in the streams – meaning to convert it to something more suitable to the task at hand. Here we take the events from the DOM and extract the mouse position.

(ref @mouse (message.map @click "mouse" (lambda [event]
  {x: event.clientX y: event.clientY}
)))

The next most important action is to filter the data to remove impurities. Here we only return results from the top left quadrant of the browser window.

(ref @top-left (message.filter @mouse "top-left" (lambda [pos]
  (< pos.x pos.y)
)))

All stream processors return the new message queue name created by adding the provided name to the source. Here @top-left is dom-click/my-message-address/mouse/top-left – very readable in a message trace.

Lastly we want to observe the result and actually do something.

(message.listen @top-left (=> (console.log @.x @.y)))

Naming queues as above provides clarity but is a bit verbose. Cascade can be used for a more concise result. In Lispz, => is an anonymous function with one parameter, @.

(cascade
  (=> (message.from.click "my-message-address" document.body))
  (=> (message.map     @  "mouse" (=> {x: @.clientX y: @.clientY})))
  (=> (message.filter  @  "top-left" (=> (< @.x @.y))))
  (=> (message.observe @  (=> (console.log @.x @.y))))
)

Map and filter are easy to implement on the base messaging code. The are clean functional code with as much referential integrity as a function that sends messages can claim.

Sometimes it is not possible to provide the processing needed without keeping some state in the stream. For those evil beings, the action function provides a stateful object unique to the stream.

...
  (=> (message.filter  @  (lambda [packet context]
    (ref moved-x (isnt packet.x context.last-x)
    (context.update { last-x: packet.x })
    moved-x
  )))
  (=> (message.observe @  (=> (console.log @.x @.y))))
)

In Conclusion

So, how does it meet the other criteria in the reactive manifesto?

Responsive

An application is responsive when an event is acted upon more quickly than the observer can measure. Many systems fire off lightweight threads whenever action is required. With Lispz messages the system will be responsive if the attached functions complete in an interval less than the next likely event. It is the responsibility of the called function to call (yield) or (delay) if the process is to take time. This is the essence of co-operative multi-tasking. I considered using yield as an integral part of map and filter, but it seems an unreasonable overhead when most translation and filters are fast.

resilience

Resilience is the ability of the application or component to keep operating even after failures have occurred. For Lispz messages, this is built-in as long as the provided functions exhibit referential integrity. Resilience is still possible if the action functions hold state, but more care must be taken. If any map or filter fails then the rest of the stream is not informed, but any subsequent events are processed as usual.

elasticity

Elasticity is responsiveness under load. Again this is the responsibility of the action writer. A stream that catches all mouse move events and writes the event object to the console is not elastic. If you move the mouse fast and far then it will take time for all the messages to write.

Advertisements

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s