#1999 Reactive Extensions for Fantom

Yuri Strot Thu 9 Aug 2012

One feature I really miss in Fantom is data binding. Actually not data binding itself, but some easy way to make data observable. Something like JavaFX Script binding or generating EMF notification model. There were a lot of discussions around this topic and I believe one day these stuff will find first class support in Fantom. But for now there is lack of tools for this quite common task. I've used https://bitbucket.org/qualidafial/fantom-bind for some time. It helps with data binding, but still too verbose when you want to define new "bindable" data.

It looks obvious to use Fantom DSL for this task and finally I have tried to build a simple library to fill the gap between data and bindability. And I could say the result exceeded all my expectations! Therefore I want to share my work with the community and introduce new rx pod: https://bitbucket.org/ystrot/rx/.

Intro

The basis of the rx library is observable sequence concept introduced by Microsoft LINQ and Rx Framework. It's just the simplest and the best way to organize event streams. However I didn't try to port MS Rx to Fantom like it was done for RxJS. Instead I got the basic idea, adapt it and built observable objects on top using Fantom DSL.

There is no need to go deep into MS Rx to understand rx library. I'll just give quick overview. However if somebody miss this concept I recommend you to read this quite old, but still the best introduction to Rx I saw: http://themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html.

Core

Observable sequence in the rx library represented with two mixins: Rx and Handle. The base part of these mixins is amazingly simple:

mixin Rx
{
  abstract Handle attach(|Obj?| f)
}

mixin Handle
{
  abstract Void detach()
}

In other words, Rx represents source of some events and allow you to attach/detach callback function to handle event appearance (similar to fwt::EventListeners). Moreover, Rx give you an ability to manipulate event streams like a list: filter, map, reduce, etc.

The easiest way to play with Rx object is to use RxBin class. It provides a default implementation of the Rx as well as RxBin.push method to propagate an event to registered observers. That's how it looks:

bin := RxBin()
bin.each    { echo("origin: $it") }
   .exclude { it <= 0 }
   .map     { it->mult(2) }
   .attach  { echo("result: $it") }

bin.push(2)
bin.push(1)
bin.push(0)

The result of the execution:

origin: 2
result: 4
origin: 1
result: 2
origin: 0

There are a lot of interesting things about the core part, but I want to keep it out of this little introduction. For now all we need to know is that we have that nice observable sequences.

Reactive Objects

The real power of rx library coming from the integration with Fantom objects. Let's take a look at the following example:

class RxObject
{

  Int var := Rx<| 0 |>

  Void main()
  {
    Rx.fromField(this, #var).attach { echo("var = $it") }
    var = 1
    var = 2
  }

}

As you can guess, result of the execution is:

var = 1
var = 2

The magic came up from this simple DSL initialization Rx<| ... |>. Every field initialized this way become reactive. It means that changes of this field represents observable sequence which can be accessed through Rx.fromField method. As you can see Rx framework provides easy and powerful way to implement observer pattern.

Another useful thing is integration with Fantom reflection. We can use Rx.fitsField(Field) method to check whether a field is reactive. It gives us ability to work on the meta-level with observable objects:

Rx onAnyChange(Obj o)
{
  o.typeof.fields.findAll { Rx.fitsField(it) }
   .reduce(Rx.empty) |Rx rx, f| { rx.merge(Rx.fromField(o, f)) }
}

This two-line method allows to observe all changes of any object which use Rx framework. Autosave becomes trivial.

Reactive Expressions

Since we have powerful observable sequences and very simple way to make our data reactive there is just one step to data binding. Reactive expressions is the step we need. Let's see how it works:

class Name
{

  Str first := Rx<||>

  Str last := Rx<||>

}

class Person
{

  Name name := Rx<||>

  override Str toStr := Rx<| "$name.first $name.last" |>

  Void main()
  {
    Rx.fromField(this, #toStr).attach { echo(it) }
    name = Name { first = "Yuri"; last = "Strot" }
    name = Name { first = "Andy"; last = "Frank" }
    name.first = "Brian"
  }
}

The output of the execution is:

Yuri Strot
Andy Frank
Brian Frank

I hope this example is self-explaining, but there are several interesting things.

In Name definition we keep Rx<||> DSL empty. It's because Rx can find default value for us, it just checks defVal and default constructor. As you can see it works for Person.name as well.

The most interesting things appear in the Person.toStr:

  • First, we override toStr method and make it a field (yeah, it's possible in Fantom).
  • Second, we make toStr field observable and use this to attach one observer in the main method.
  • Finally, we bind toStr field to other Rx fields. Now all name changes will be propagated to the toStr field.

The really cool thing about reactive expressions is that you can use mostly all expressions inside Rx DSL and it will work. You can combine both reactive and standard fields, use method calls, construct new objects, etc.

Limitations

First of all, it's just a prototype. However it works pretty well even in JavaScript! I'm already start using it in other projects and quite happy with this solution.

Few limitations coming from the Fantom restrictions. There is no generic, so Rx events are plain Obj. There is no ability to create custom list/map, so there are no observable lists and maps.

As for Rx expressions, there is no support for closures and conditions for now. But I think I will add this support some time.

The last thing I want to mention is the lack of documentation. For now you can find some fandoc and use tests as examples. However if somebody (except me :-) find this library useful I will give more efforts and create some guides.

brian Thu 9 Aug 2012

This is really awesome Yuri. I'm a big fan of the Microsoft Rx stuff. It is an elegant way to combine event streams with functional style filter, map, etc. It was also a big inspiration for much of the early SkySpark work. So it is cool to see a Fantom implementation - it is giving me lots of ideas.

It is also another use case where DSLs can be a bit awkward for annotating a method or field for new functionality (basically facets for compile-time). I definitely think we can do something more elegant and easy to use there than having to muck with the AST at compile time (maybe something more like AOP where we declare hooks for pre and post processing of a field set or method call).

Yuri Strot Thu 9 Aug 2012

Thanks, Brian! DSL definitely doesn't look perfect at this place. Initially I was thinking about special kind of facet which works like DSL and provides ability to modify AST as compile time:

@Rx Name name := Name()

Actually custom DSL plugin wasn't as hard as I expect. Moreover compiler API is really great! It was a pleasure to work with it. However AOP approach looks very interesting.

DanielFath Thu 9 Aug 2012

I think we should keep the DSL. They don't cause bizzare incantation and they show clear intent. The problems current DSLs have:

  1. They are hard to discover by tooling (coloring, auto-complete)
  2. Compared to XText they seem a lot more complicated to make (but thank god they don't need 1000 classes to be run)

Login or Signup to reply.