June 29, 2016

0.6.1: Reactive Programming

Despite being a minor release, 0.6.1 still weighs a heavy 588 commits with a big number of fixes and many additions, among which the new reactive framework is the most notable.

Last release introduced, for the first time in the Rebol world, a reactive programming framework, as a part of the GUI engine. While working on improving it, we realized that it could actually be easily generalized beyond GUIs, with just minor changes to its design and implementation.

What is reactive programming?

Let me make a short disclaimer first, this is not yet-another-FRP framework relying on event streams. The reactive model we use is known as object-oriented reactive programming (using a "push" model), which is both simple to understand and close to spreadsheet's model (i.e. Excel formulas). That model has often been praised for its simplicity and efficiency. You can now use it directly in Red.

So, in practice, what is it?  It is a way to link one or more object fields to other fields or global words, by specifying relationships in a block of code (can be a single expression or a complex multi-step computation). Each time a source field value changes, the target value is immediatly updated, you don't have to call a function for that, it's pretty much define-and-forget. ;-) Here's a simple example in Red:
    red>> a: make reactor! [x: 1 y: 2 total: is [x + y]]
    == make object! [
        x: 1
        y: 2
        total: 3
    ]
    red>> a/x: 5
    == 5
    red>> a/total
    == 7
    red>> a/y: 10
    == 10
    red>> a/total
    == 15
In that example, the is infix function is used to create a reactive relation between the total field and the x and y ones using a simple formula. Once set, total is refreshed automatically and asynchronously each time the other fields are changed, regardless how, where or when they are changed! It's the same concept as spreadsheet cells and formulas, just applied to object fields.

This reactive programming style belongs to the dataflow programming paradigm. It doesn't enable you to write code, that you wouldn't otherwise be able to write in an imperative style. Though, it helps reduce the size and complexity of your code, by abstracting away the "how" and helping you focusing more on the "what" (not dissimilar to FP). The gains of such approach become significant when you chain together many relations, creating graphs of, more or less complex dependencies. GUI programming is where it shines the most, as nodes are visible objects, and reactions produce visible effects.

Here is a comparative example with a reactive GUI vs the non-reactive version:

Let's make a simple native GUI app using VID, Red's graphic DSL (we call it a dialect). It will just provide 3 sliders, which control the R, G, B components of the box's background color.


The reactive version:
    to-int: function [value [percent!]][to integer! 255 * value]
    
    view [
        below
        r: slider
        g: slider
        b: slider
        base react [
            face/color: as-color to-int r/data to-int g/data to-int b/data
        ]
    ]
The non-reactive version:
    to-int: function [value [percent!]][to integer! 255 * value]
    
    view [
        below
        slider on-change [box/color/1: to-int face/data]
        slider on-change [box/color/2: to-int face/data]
        slider on-change [box/color/3: to-int face/data]
        box: base black
    ]
What can we say about the non-reactive version?
  1. Size is pretty much the same, though the non-reactive version has more expressions and code looks denser.
  2. The updating code is spread over 3 event handlers.
  3. The face word in each handler refers to the widget, so we can remove the slider names (very minor gain though).
  4. The box face needs a name (`box`), so it can be referred to, from the event handlers.
  5. The box face default color is grey, so it needs a `black` keyword to force it to the right default color (as the sliders are all at position 0 on start). The reactive version sets the right color on start, no need to care about it.

Even in this simple example, we can see that the complexity, and the cognitive load are higher in the non-reactive version. The more relationships can be modeled using reactions in a GUI app, the higher the gains from using the reactive approach.


Red reactive framework

Red's reactive framework is just ~250 LOC long, and written purely in Red (no Red/System). It relies on object events (equivalent to observables in OO languages) and the ownership system (which will be properly documented once completed in one or two releases time). Rebol does not offer any past experience in such domain to guide us, so it should still be considered experimental, and we need to put it to the test in the wild, to study the pros/cons in real-world applications. We are quite excited to see what Red users will create with it.

Full documentation for the reactive framework is available here. It also explains the important difference between static and dynamic relations.

In a nutshell, the reactive API provides 4 functions (quite big API by our standards):

  • react to create or remove reactions.
  • is infix function for creating reactions which result will be assigned.
  • react? to check if an object's field is a reactive source.
  • clear-reactions to remove all existing reactions.

Moreover, react is directly supported as a keyword from VID dialect. That's all you need! ;-)

Here is a simple demo linking together a couple dozen balls, following each other. Source code is available here.



Let's now have a look at other features brought by this release.

Time! datatype

A time! datatype is now included in Red, supporting already a broad range of features, like:
  • Path accessors: /hour, /minute, /second.
  • Math operators, including mixing with other scalar types.
  • All comparison operators.
  • Actions: negate, remainder, random, pick.
    red>> t: now/time
    == 12:41:52
    red>> t + 0:20:00
    == 13:01:52
    red>> t/second
    == 52.0
    red>> t/hour: t/hour - 5
    == 7
    red>> t
    == 7:41:52

GUI changes

Two main additions to our View engine have enabled the writing, or porting, of some nice graphic demos (thanks to Gregg Irwin for the awesome demos!). Here are a few examples:

Bubble demo

Gradient Lab

Particles demo


View engine changes
  • New time event in View, triggered by timers.
  • New rate facet in face! objects for setting timers.
  • move action allows to move faces between panes in a non-destructive way.
  • Adds support for event/window property.
  • data syncing with text for field and text faces.
  • Add default option for fields (e.g. options: [default 0]).
  • at, skip, pick, poke, copy on image! now accept pair! index argument.
  • Added /argb refinement for image! datatype.
  • Added request-font dialog.
  • Improved size-text native.
  • GUI console faces are now excluded from the View debug logs.

Draw dialect changes
  • fill-pen has been extended to support color gradients.
  • pen accepts `off` as argument now, to make the subsequent pen-related operations invisible.
  • Allows box to accept edges in reverse order.
  • Radius of circle now accepts a float! value.
  • Added key-color support for to image command.

VID dialect changes
  • Added rate keyword for setting timers.
  • do command now support `self` to refer to container face (window or panel).
  • Added focus option to faces for presetting focus.
  • Added select option support to preselect an item in a list (using an integer index).
  • Added default option support for field and text faces' default data facet value.
  • Added support for get-words to pass an handler function as an actor.
  • Adding glass and transparent color definitions.

The red/code repository has also been filled with more demos using the new features, like color gradients and timers.

Other changes

New actions: change, move

New natives: remove-each, new-line, new-line?, set-env, get-env, list-env, context?, enbase, now/time, browse

New functions: repend, overlap?, offset?, any-list?, number?, react, is, react?, clear-reactions, dump-reactions, make-dir, request-font, time?

Parse improvements
  • Added change command.
  • remove also accepts, now, a position argument.
  • Support for parsing binary! series.
  • Several bugs fixed.
Syntax for change command:
  • CHANGE rule value
  • CHANGE ONLY rule value
  • CHANGE rule (expression)
  • CHANGE ONLY rule (expression)
  • CHANGE pos value
  • CHANGE ONLY pos value
  • CHANGE pos (expression)
  • CHANGE ONLY pos (expression)
Example using rule syntax:
    a: "12abc34"
    alpha: charset [#"a" - #"z"]
    parse a [some [to alpha change [some alpha] dot]]
    a = "12.34"
Example using pos syntax:
    a: "12abc34"
    alpha: charset [#"a" - #"z"]
    parse a [some [to alpha b: some alpha change b dot]]
    a = "12.34"

Console improvements
  • Filenames completion using TAB key.
  • Font and color settings from new menu bar.
  • Ctrl-K will erase to end of line (CLI console).
  • Ctrl-D will remove character or exit like Ctrl-C if empty line (CLI console).
  • Optimized speed of pasted code in console.
Other improvements
  • Allows bitsets to be used as search pattern for find on any-string! series.
  • /next refinement support for do and load.
  • /seek and /part refinements added to read.
  • Added any-list! typeset.
  • Added /with refinement to pad function.
  • Improved split function (though not final version).
  • Added LF <=> CRLF conversions support to UTF-16 codec.
  • input can now read from stdin when run from  a child process.
  • Added /same refinement into find and select actions.
  • Added binary! support for data and HMAC key to checksum.
  • Reduced emitted code for setting struct members to float literals on IA-32.
  • Allows owned property to be used by modify on objects.
  • Compiler now accepts creating global op! values from object's functions.

A big number of tickets have also been processed, 110 bug fixes have been provided since 0.6.0 release. We have about 10% of open tickets which is more than usual, though not surprising after the last huge release, but only 22 are actual bugs. Thanks for all the contributors who reported the issues and helped fix them, Red owes you a lot!


What's next?

On the road to Android support, we need to be able to properly wrap a Red app in a shared library, which is the main focus for the next release. Moreover, being able to build the Red runtime library only once, will greatly reduce compilation time (the runtime library is currently rebuilt on each compilation). As the work on this new feature is already quite advanced, we expect next release to occur during July, even if we always favor quality over arbitrary deadlines. ;-)


In the meantime, enjoy the new release!

Fork me on GitHub