reactive-web: Lift をシンプルに

SeqSignal

SeqSignal は、reactive の最も重要で強力な機能の 1 つです。たとえば、連絡先のリストが表示されているとしましょう。この場合、連絡先の追加、削除、または更新が行われるたびに、表示を更新する必要があります。1 つの方法として、リストを Signal[Seq[Contact]] に保持し、変更イベントをリスンし、それに応じて表示を更新するやり方があります。このとき表示は、map された Signal によって表現されることになります。しかしこのやり方では、単一の行が変更された場合でも全体の表示を更新する必要があり、特に連絡先が多い場合には高くつきます。

このような場合のために用意されているのが、Signal の特別なサブタイプである SeqSignal です。SeqSignal では、Signal のメンバに加えて、deltas という名前の追加の EventStream[SeqDelta] が追加されています。チェイン内で最初の SeqSignal をどのように作成するかに応じて、SeqSignal は挿入、削除、および更新を表す SeqDelta イベントを発火します。さらに、複数の操作を 1 つのイベントにバッチ的にまとめることができるので、たとえば複数の削除を行う場合でも、再描画を別々に複数回行う必要はありません。SeqSignalmap または flatMap すると、結果として得られる SeqSignal は delta イベントを発火します。このとき、これらの delta イベントが、元の SeqSignal によって発火された delta イベントとどのような関係になるかは、map または flatMap に渡す関数によって定義されます。

SeqSignal[T]Signal[Seq[T]] を継承していることに注意してください。 つまり、要素を SeqSignal に変換する場合には、map を別の map 内にネストする必要があります。具体的には、SeqSignal[T] / Signal[Seq[T]]map メソッドに対し、Seq[T] を操作する関数を渡す必要があり、この Seq の内側の要素を操作することが目的なので、それに対して map を呼び出す必要があります。

// in.deltas が 3 の挿入を発火すると、
// 返される SeqSignal の deltas は
// 30 の挿入を発火する.
// また、now と change のふるまいは
// Signal から継承したものになる.
def timesTen(in: SeqSignal[Int]) =
  in.map { _ map (_ * 10)}

SeqSignal の作成

SeqSignal を作成するにはどうすればよいでしょうか。

1 つの方法は、サブタイプ BufferSignal を使うやり方です。BufferSignal を作成する最も簡単な方法は、そのファクトリを使うことです (あとの例を参照してください)。BufferSignal は 2 つの方法で変更できます。1 つは def value を使う方法で、これが返す ArrayBuffer は直接変更することができ、各変更によって、対応する deltas が発火されます。もう 1 つの方法は、Var を更新するときと同じ構文を使用する方法で (例を参照してください)、この場合は古い値と新しい値の間で差分が計算されます。def comparator をオーバーライドすれば、差分アルゴリズムが使用する等価テストを変更できます。

val bufferSignal = BufferSignal(1, 2, 3, 4, 5)
bufferSignal.value += 6  //  挿入を発火
bufferSignal ()= List(2, 3, 4, 5, 6, 7)  // 削除と挿入を発火

SeqSignal を作成するもう 1 つの方法は、SeqSignal ファクトリを使う方法です。このファクトリは、Signal[Seq[T]] を取り、SeqSignal[T] を返します。SeqSignal[T] の deltas は、すべての変更に対して差分アルゴリズムを実行することによって計算されます。immutable な SeqSignal を作成するには、Val を渡します。

val simpleSignal = Var(List(1,2,3))
val seqSignal = SeqSignal(simpleSignal)
simpleSignal ()= List(2,3,4) // seqSignal.deltas は
                             // 削除と挿入を発火する.