Monday, June 11, 2012

Reactive Programming Tutorial in Scala + GWT. Signal.

This is the second (and last) part of the tutorial. All those Scala live examples have been compiled to JavaScript and are running inside your browser. The first part of the tutorial is here.

-----

Signal


Introduction


While EventStream represents a stream of discrete values over time — that is, each value only exists instantaneously (in practice that means that you can never say "what is the current value?"), Signal represents a continuous value. In practical terms, a Signal has a current value, and an EventStream that, whenever the Signal's value changes, fires the new value.

now


You can get the current value of a Signal by calling now. However, functional style means that most of the time it should be avoided, and you should find a way to have Signal call your code with the value rather than you asking the Signal for it.

change


This is an EventStream that fires an event for every change to the Signal's value.

signal.change.foreach {v =>
  assert(v == signal.now)
}


Creating a Signal


The two simplest Signal classes share the names of two important Scala keywords. They are Val and Var. Val lets you create an immutable Signal. Calling now will always return the value it was created with. change will never fire an event. Var lets you create a mutable Signal. You can update it by writing myVar() = newValue, which is syntactic sugar for myVar.update(newValue).

object MyApp extends Observing {
  val myVal = Val(72)
  val myVar = Var(31)
  myVar.change foreach println
  myVar() = 29  // prints 29
}

You can create a Signal from an existing EventStream by calling its hold method. And you can create a Signal by transforming another Signal via map and flatMap.

foreach


Just as you can react to events with EventStream#foreach, you can do something for all values of a Signal with foreach. Passing a function to Signal#foreach is equivalent to executing the function and then calling foreach on the change EventStream with the function.

map


Just like you can map an EventStream to get a new, transformed, EventStream, you can map Signals too. The resulting Signal will reflect the transformation expressed in the mapping function, both in its definition of now and in the events change fires.

val myVar = Var(3)
val mapped = myVar.map(_ * 10)
println(mapped.now)  // prints 30
myVar() = 62
println(mapped.now)  // prints 620



flatMap


Just like you can flatMap an EventStream to get an EventStream that "switches" between several EventStreams, so too you can create a Signal whose value depends on several other Signals. However, there are several differences from EventStream's flatMap, and its usage is slightly different. These differences stem from the fact that a Signal always has a value. So the semantics are, that initially the resulting Signal has the value of the Signal created by applying the function passed to flatMap to the current value of the parent Signal. This is reflected in now as well as in change.

val myVar1 = Var(72)
val myVar2 = Var(69)
val myVar3 = Var(false)

val flatMapped = myVar3 flatMap {
  case true => myVar1
  case false => myVar2
}
println(flatMapped.now)  // prints 72
myVar3() = true
println(flatMapped.now)  // prints 69
myVar2() = 2
myVar1() = 1
println(flatMapped.now)  // prints 2
myVar3() = false
println(flatMapped.now)  // prints 1

Another example: filtering a list based on an entered string.

def filteredList(filterSignal: Signal[String],
                 itemsSignal: Signal[Seq[String]]) =
  for {
    filter <- filterSignal
    items <- itemsSignal
  } yield items.filter(s => s.indexOf(filter) >= 0)
/* The above desugars to:
filterSignal.flatMap{ filter =>
  itemsSignal.map{ items =>
    items.filter(s => s.indexOf(filter) >= 0)
  }
}
*/

Similarly, you can pass flatMap a function that returns EventStreams, and flatMap will return an EventStream that “strings together” the event streams that correspond to the signal’s values.

For instance, suppose you have an application that swaps the meaning of the mouse buttons while the Alt key is depressed. You have two EventStreams, one firing left mouse button clicks, and one firing right mouse button clicks, and a Signal representing the Alt key's state.

val selectClicks = altKey flatMap (if(_) leftButtonClicks
                                   else rightButtonClicks)
val contextClicks = altKey flatMap (if(_) rightButtonClicks
                                    else leftButtonClicks)




Passing down state: foldLeft


If you need to derive a signal based on memory of previous state, use foldLeft. It works similar to EventStream.foldLeft.

zip


A method that can be convenient sometimes is zip. A parallel to the collections' zip method, it allows you to create a Tuple2-valued Signal from two Signals.

def nameAndAge(name: Signal[String],
               age: Signal[Int]): Signal[(String,Int)] = name zip age

Preventing infinite loops


When you have two interdependent signals, you need a way to prevent infinite loops (signal A caused signal B to change which causes signal A to change). Signal has two methods that return a new Signal identical to the parent Signal but with an added safety filter. distinct returns a new Signal that filters out change events that are equal to the signal's previous value. This suffices in most cases. But what if when signal A causes signal B to change signal A, it sets to another value, infinitely? A silly illustration:

myVar.map(_ + 1) >> myVar

Another example is where rounding errors are not symmetric. For such scenarios, call nonrecursive, which uses a DynamicVariable (Scala's ThreadLocal) to prevent recursion.

Time-consuming handling of values


Similar to EventStream, Signal has nonblocking and zipWithStaleness methods.


SeqSignal

Here is one of the most important and powerful features of reactive. Suppose you have a list of contacts displayed. Whenever a contact is added, removed, or updated, you need to update the display. Well, you keep the list in a Signal[Seq[Contact]], listen to change events, and update the display in response. You could have the display be represented by a mapped Signal. But it's expensive, especially with a lot of contacts, to update the entire display whenever a single row changes.

For this reason, there is a special subtype of Signal, SeqSignal. It adds, on top of Signal's members, an additional EventStream[SeqDelta] called deltas. Depending on how you create the first SeqSignal in the chain, it will fire SeqDelta events representing inserts, removes, and updates. In addition multiple operations may be batched in a single event, so for example multiple deletions do not need to result in multiple, separate repaints. When you map or flatMap a SeqSignal, the resulting SeqSignal fires delta events whose relationship to the delta events fired by the orignal SeqSignal is defined by the function you pass to map or flatMap.

Remember that SeqSignal[T] extends Signal[Seq[T]]. So if you want to transform the elements a SeqSignal, you need to nest one map inside another: you have to pass the map method on SeqSignal[T] / Signal[Seq[T]] a function that operates on a Seq[T], and since you want to operate on the elements inside that Seq, you need to call map on it.

// When in.deltas fires an Insert of 3,
// the returned SeqSignal's deltas will
// fire an Insert of 30,
// besides that now and change will
// have the behavior inherited from Signal
def timesTen(in: SeqSignal[Int]) =
  in.map { _ map (_ * 10)}


Creating a SeqSignal


How do you get a SeqSignal?

One way is subtype BufferSignal. The easiest way to create a BufferSignal is with its factory (see example below). You can modify it in two ways: Either use def value, which returns an ArrayBuffer which you can mutate directly, with each mutation resulting in the corresponding deltas being fired; or use the same syntax you use to update Vars (see example), which will result in diff being calculated between the old and the new values. You can override def comparator to change the equality test used by the diffing algorithm.

val bufferSignal = BufferSignal(1, 2, 3, 4, 5)
bufferSignal.value += 6  // fires an Insert
bufferSignal() = List(2, 3, 4, 5, 6, 7)  // fires a Remove and an Insert

The other way is to use the SeqSignal factory, which takes a Signal[Seq[T]] and returns a SeqSignal[T] whose deltas are calculated by running the diffing algorithm on every change. To create an immutable SeqSignal, just pass it a Val.

val simpleSignal = Var(List(1,2,3))
val seqSignal = SeqSignal(simpleSignal)
simpleSignal() = List(2,3,4) // seqSignal.deltas fires
                             // a Remove and an Insert.

-----

Now that we know the basic blocks of reactive programming, it's time to create some useful applications.

But first some comments on the differences between JVM and GWT versions this library. JavaScript doesn't have WeakReference equivalent, so underlying listeners will not be garbage collected so automatically in GWT version. Instead an instance of Observing registers all listeners, and they all can be removed at once by the call to observing.removeAllListeners. It's a good idea to integrate this call into a higher level framework, so that we don't need to call it by hand. For example, when the user navigates to a different view, all listeners could automatically be removed. This is precicely what I did. We'll see it in real applications later.

Another difference is that method "nonblocking" is not yet implemented in reactive-gwt. It is not difficult to implement it with similar semantics, but with JavaScript limitations. I just didn't have enough time and a justifying use-case.

It is a good idea to become more familiar with GWT before creating useful applications with it. Let's evaluate GWT pros and cons.

No comments:

Post a Comment