Getting a Good Grasp of F# (仮)

関数型言語F#をもっと楽しみたい

F# と Sodium でリアクティブ・プログラミング(2)

Sodium のCellとアキュームレータの作成

Sodium のStreamが値を持つのはイベント発生の瞬間であるのに対して、Cellはいったん獲得した値を保持できる。Cellの値は書き換え可能で、次の書き換えまでその値を保持しており、いつでも自由に読み出すことができる。

本記事は SodiumFRP.FSharp を使用する。

www.nuget.org

以下に示すサンプルコードの動作は、

  • コンソール画面で、キー入力を待つ
  • 入力した文字をコンソール画面に出力する。ただし、数字とアルファベット以外のキー入力は無視する。
  • 入力が数字キーならば、数字キーが累積で何回押下されたかをコンソール出力する。
  • 入力がアルファベットキーならば、アルファベットキーが累積で何回押下されたかをコンソール画面に出力する。
  •  Esc キーを押せば、入力受付をやめてアプリケーションを終了する。

サンプルコード


サンプルコードの解説
/// キー入力イベント用ストリーム
    let sKeyInput: StreamSink<ConsoleKeyInfo> = StreamSink.create()

7行目:キー入力イベントを流すStreamSink<ConsoleKeyInfo>としてsKeyInputを作成(StreamSinkStream型の派生クラス)。StreamSink型はsendS関数(49行目で使用)を使って明示的にイベントを送信できる。


use lKeyInput: IStrongListener =
        sKeyInput
        |> mapS (fun (ki: ConsoleKeyInfo) ->
            match ki.KeyChar with
            | c when Char.IsNumber c -> Some c 
            | c when Char.IsLetter c -> Some c
            | _ -> None)
        |> filterOptionS
        |> listenS (fun (c: char) -> printfn $" %c{c}")

9~17行目:キー入力イベント用ストリームsKeyInputStreamSink<ConsoleKeyInfo>型)を、mapS関数とfilterOptionS関数を使ってStream<char>に変換する。また、数字・アルファベット以外のキー入力イベントを破棄する。
更にlistenSを使ってリスナー(IStrongListener型)を作成(17行目)、イベントデータ処理用のリスナー関数を登録する。このリスナー関数では入力文字をそのままコンソール出力する。


/// 数字キーを押した回数を保持するセル
    let cNumberCounter: Cell<int> = 
        loopWithNoCapturesC (fun (lpcCount: LoopedCell<int>) -> 
            sKeyInput
            |> snapshotC lpcCount (fun (ki: ConsoleKeyInfo) (acc: int) -> 
                                                if Char.IsNumber ki.KeyChar then acc + 1 else acc) 
            |> calmS
            |> holdS 0)

20~26行目loopWithNoCapturesC関数を使って、数字キー押下の累積回数を保持するアキュームレータとなるcNumberCounterCell<int>型)を作成する。ストリームであるsKeyInputにてイベントが発生する毎に、lpcCountLoopedCell<int>型)の値を読み取り、数字キー入力と判定された場合のみカウンタ値をインクリメントして保持している。(LoopedCellCell型の派生クラス)

calmS関数を使うことにより、Streamで発生したイベントの値が直前イベントと重複した場合にはイベントを破棄している。


/// アルファベットキーを押した回数を保持するセル
    let cLetterCounter: Cell<int> =
        sKeyInput
        |> accumS 0 (fun (ki: ConsoleKeyInfo) (acc: int) -> 
            if Char.IsLetter ki.KeyChar then acc + 1 else acc)
        |> calmC

34~38行目accumS関数を使って、アルファベットキー押下の累積回数を保持するアキュームレータとなるcLetterCounterCell<int>型)を作成する。このとき、calmC関数を使うことにより、Cellの値の更新が発生しても既存の値から変化しないときはリスナー呼び出しを行わないように設定できる。

SodiumFRP.FSharp においては、アキュームレータを実現する方法は複数用意されている。本記事のサンプルコードのように、処理が単純なアキュームレータならば、21行目loopWithNoCapturesCを使うよりもaccumS関数の方が簡潔な記述ができる。

 

※ソースコードはGitHubで公開しています

《参考資料》

関数型リアクティブプログラミング Functional Reactive Programming