[SodiumFRP + F#] アキュムレータロジックを構成するいくつかの方法

# 概要
関数型リアクティブプログラミング（SodiumFRP）の練習として、**アキュムレータロジック**を構成するためのコードについて整理してみました。

## アキュムレータ

プログラミングの世界のなかでの**アキュムレータ**についてWikipediaでは、

> 演算装置による演算結果を累積する、すなわち総和を得るといったような計算に使うレジスタや変数のことである。

・・・のように説明しています。



これを、SodiumFRPの世界・用語で説明すると、**既存の情報（セルの値）と、新しい情報（ストリームの値）を組み合わせによって更新される値を持つ情報コンテナ（セル）**となります。

例えば、**値を合計していくアキュムレータ（セル）**は、初期値「0」を与えておいたうえで、順に「5」「20」「10」という値のストリームを受けると、その値を「0（初期値）」→「5」→「25」→「35」と更新するように振舞います。

このような値を合計していくアキュムレータについて（プリミティブな要素の組合せで）どのようにロジックを構成すればよいか？を、書籍『関数型リアクティブプログラミング』@技術評論社では、**Java + SodiumFRP** でのコードを使いながら丁寧に解説しています（セクション2.9）。

ここでは、**F# + SodiumFRP** で同様のアキュムレータを組み上げるコードを複数パターン示していきます。

# (1) CellLoop + snapshotC で構成
書籍『関数型リアクティブプログラミング』で示しているコードを単純にF#に書き換えたものになります。なお、F#では先頭文字以外ではシングルクォーテーションを変数名に利用することができます。シングルクォーテーションが付いた`sum'`という変数が特別な意味を持つわけではありません。

```FSharp:CellLoop+snapshotCで構成
open System
open SodiumFRP
open System.Threading

let str o = o.ToString()

[<EntryPoint>]
let main argv =

  let sUserInput = StreamSink.create<int> ()

  (* アキュムレータを構成 *)
  let sum = Transaction.run( fun () -> 
    let sum' = CellLoop<int> ()
    let sUpdate = sUserInput 
                  |> snapshotC sum' (fun s v -> s + v) 
    do sum'.Loop( sUpdate |> holdS 0 )
    sum'
  ) 
  (* ここまで *)

  let l = sum 
        |> listenC (fun s -> Console.WriteLine("更新 sum -> " + (str s)))

  Thread.Sleep(1000)
  sUserInput |> sendS 5  // 値5を内容とするストリーム（sUserInput）を発火 　
  Thread.Sleep(1000)
  sUserInput |> sendS 20
  Thread.Sleep(1000)
  sUserInput |> sendS 10

  do Console.ReadKey() |> ignore
  do l.Unlisten()
  0
```

ここで`sUserInput |> sendS 5`は、GUIなどを通してのユーザから入力イベントを模しています。これを実行すると、結果は次のようになります。

```
更新 sum -> 0
更新 sum -> 5
更新 sum -> 25
更新 sum -> 35
```

まず、すぐに`更新 sum -> 0`が出力され、1秒後に`更新 sum -> 5`、さらに1秒後に`更新 sum -> 25`、さらに1秒後に`更新 sum -> 35`が表示されます。ユーザからの入力に対してリアクティブな（反応的な）プログラムになっていることが分かります。

# (2) CellLoop + mapS + sampleC で構成

次のコードは、`shapsotC`関数の代わりに`sampleC`関数を使った例です。(1)で示したコードの`(* アキュムレータを構成 *)`から`(* ここまで *)`の部分を差し替えます。

```FSharp:CellLoop+mapS+sampleCで構成
  let sum = Transaction.run( fun () -> 
    let sum' = CellLoop<int> ()
    let sUpdate = sUserInput 
                |> mapS (fun v -> (sampleC sum') + v) 
    do sum'.Loop( sUpdate |> holdS 0 )
    sum'
  ) 
```

実行結果は(1)と同じです。

# (3) loopWithNoCapturesC + snapshotC で構成

(1)と(2)のコードでは、`do sum'.Loop( sUpdate |> holdS 0 )`という部分が（`sum'`というオブジェクトの内部状態の変更を行なっていることから）関数型プログラミングらしくないので、これを内部に隠蔽したものが次の`loopWithNoCapturesC`関数になります。

```FSharp:loopWithNoCapturesC+snapshotCで構成
  let sum = loopWithNoCapturesC ( fun sum' ->
    let sUpdate = sUserInput 
                |> snapshotC sum' (fun s v -> s + v) 
    sUpdate |> holdS 0 )
```

実行結果は(1)と同じです。

# (4) loopWithNoCapturesC + mapS + sampleC で構成

```FSharp:loopWithNoCapturesC+sampleCで構成
  let sum = loopWithNoCapturesC ( fun sum' ->
    let sUpdate = sUserInput 
                |> mapS (fun v -> (sampleC sum') + v) 
    sUpdate |> holdS 0 )
```

実行結果は(1)と同じです。

# (5) accumS で構成

ヘルパー関数`accumS`を使用した例です（この関数は内部的にはloopとsnapshotを使って実装されているようです）。非常にシンプルに書けます。

```FSharp:accumSで構成
  let sum = sUserInput |> accumS 0 (fun s v -> s + v)  
```

実行結果は(1)と同じです。

# (6) 悪い例

```FSharp:accumSで構成
  let mutable exSum = 0  // 可変な変数
  let sum = sUserInput 
          |> mapS ( fun v -> 
                    exSum <- exSum + v
                    exSum )
          |> holdS exSum 
```

実行結果は(1)と同じなのですが、これはFRP外部にある可変変数`exSum`について読み取り＆書き替えをしており（mapSの引数に渡される関数（ラムダ式）が参照透過的ではない）、FRPが保証してくれるはずの合成性を破綻させてしまう非常に悪い例です。