bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

Reactive-bananaの紹介(2)

3.Arithmetic

reactive-banana-bxには例題が用意されている。今回は、その中の一つであるArithmeticについて、そのプログラムの内容を解説する。

Arithmeticは、整数の足し算を実時間で実行してくれる。画面上に二つのエントリーが用意されていて、一つは被加算の整数を、他の一つは加算の整数を入力できるようになっている。数値が入力されると、それらの数の和が瞬時に結果として出力される。もし、整数以外のものが入力された時は、結果は"--"と出力される。

3.1 Graphics.Ui.WX

モジュールGraphics.Ui.WXは、GUIのためのウィジェットGUIを構成するための部品要素とその集まり)を提供してくれる。主要な部品要素はGUIの枠組み(フレーム)を提供するframeである。フレームには、エントリー、ボタンなどが含まれる。

今回は、二つのエントリーと結果の表示とが必要である。これは、プログラムでは次のように記述する。

    f         <- frame [text := "Arithmetic"]
    input1    <- entry f []
    input2    <- entry f []
    output    <- staticText f []

一番最初の行で、フレームを定義し、そのラベルをArithmeticとする。二番目と三番目の行はエントリーの定義である。それらは、input1,input2とする。四行目は出力の表示で、outputとする。

これらは、画面上に配置しなければならないが、それは、次のように行う。

    set f [layout := margin 10 $ row 10
            [widget input1, label "+", widget input2
            , label "=", minsize (sz 40 20) $ widget output]]

これにより、次のような画面が用意される。
f:id:bitterharvest:20151128110524p:plain

3.2 Reactive.Banana.Wx

関数型リアクティブプログラミングは、振舞い(Behavior)とイベント(Event)により、特徴づけられている。ウィジェットで定義された部品要素をBehaviorあるいはEventとして扱えるようにするのが、モジュールReactive.Banana.Wxの役割である。ここでは、このモジュールに含まれている関数の中で、Arithmeticで用いるものについて説明しよう。

behaviorTextは、エントリーを、reactive-bananaのbehaviorとして扱えるようにするものである。
sinkは、アニメーションに似たことを行う。これは、イベントが発生すると、それを振舞いに反映させる。今回説明しているArithmeticでは、キーが押されると、それに応じたエントリーの内容が変化し、それが、また結果にも反映される。

例えば、input1に123という整数が書き込まれていて、今、input2に対して、345という値が入力されたとする。この入力は、3というキーが最初に押されるので、それに反応して、input2には3が書き込まれる。さらにこれに反応して出力が126となる。次に4という数字が入力されるので、input2は34となり、出力は156となる。最後に、5が入力されるので、input2は345となり、出力は458となる。

3.3 Reactive.Banana

それでは、加算がどのように行われるかを調べてみよう。reactive-bananaでは二つの振舞いを合成する関数を用意している。例えば、下の図に示すような二つの振舞いがあったとする。
f:id:bitterharvest:20151128113415p:plain

今、左側の振舞いは、信号の音を調整するもので、入ってきた信号をだんだんに減衰させるものとする。左側の振舞いは信号である。左側の振舞いを、右側の振舞いが要求している強さに変換するためには、下図に示すように、時間ごとに乗算すればよい。
f:id:bitterharvest:20151128114511p:plain

active-bananaでは、振舞い間の乗算を以下のように記述する。

(*)<$>a<*>b

そこで、二つのエントリーからの加算は次のように記述する。

            result :: Behavior (Maybe Int)
            result = f <$> binput1 <*> binput2
                where
                f x y = liftA2 (+) (readNumber x) (readNumber y)

ここで、readNumberはエントリーから数値を読み込んでくる関数である。f x yはそれぞれのエントリーから整数を読み込み、それを加算する。この時、エントリーに値がまだ入っていなかったり、数字以外の文字や記号が入っている場合があるので、出力の方は、Maybe Intで値を得る(Javaなどの例外処理に当たる部分である)。

realNumberの求め方は次のようになっている。

            readNumber s = listToMaybe [x | (x,"") <- reads s]    

関数readsについての詳細はHaskellの本で調べてください。ここでは、整数が入力された時はxにその値が、そうでないときは、空のリストが返ってくると思ってください。

加算結果は関数showNumberを用いて、結果を得ている場合にはその値を、そうでない場合には"--"で出力する。以下のようになっている。

            showNumber   = maybe "--" show

3.4 プログラム全体

プログラムは、このホームページにあるが、転載する。

{-----------------------------------------------------------------------------
    reactive-banana-wx
    
    Example: Very simple arithmetic
------------------------------------------------------------------------------}

import Data.Maybe

import Graphics.UI.WX hiding (Event)
import Reactive.Banana
import Reactive.Banana.WX

{-----------------------------------------------------------------------------
    Main
------------------------------------------------------------------------------}
main :: IO ()
main = start $ do
    f         <- frame [text := "Arithmetic"]
    input1    <- entry f []
    input2    <- entry f []
    output    <- staticText f []
    
    set f [layout := margin 10 $ row 10
            [widget input1, label "+", widget input2
            , label "=", minsize (sz 40 20) $ widget output]]

    let networkDescription :: MomentIO ()
        networkDescription = do
        
        binput1  <- behaviorText input1 ""
        binput2  <- behaviorText input2 ""
        
        let
            result :: Behavior (Maybe Int)
            result = f <$> binput1 <*> binput2
                where
                f x y = liftA2 (+) (readNumber x) (readNumber y)
            
            readNumber s = listToMaybe [x | (x,"") <- reads s]    
            showNumber   = maybe "--" show
    
        sink output [text :== showNumber <$> result]   

    network <- compile networkDescription    
    actuate network