これはElm Advent Calendar 2014の11日目です。
よく知らなかったFRP(Functional Reactive Programming,リアクティブプログラミング)について調べてて少しだけElmに入門しました。
※先週やった実装なのでElmのバージョンは0.13です。いろいろ変わったらしい0.14では動かない気がします。。
■ FRPとはストリームの写像
エクセルの例をよく見かけますが、FRPとは要するに入力および出力をストリーム(時間とともに変化していく状態)として、その間を写像(マッピング)していくプログラミング手法だとざっくり理解しました。
エクセル操作を例にストリームを写像するイメージの落書きをしてみました。
A3セルの定義がA1セルとA2セルの和になっていて、A1セル/A2セルを変更するとA3セルが定義に従って更新されていきます。
ここでは下記あたりを参考にしました。
■ やってみる
良くわからないので適当な課題をでっちあげて実装してみました。FRPといえばHaskellライクなaltJSのElmらしい。というわけでElmを使ってみました。
● 課題
入力と出力が下記のようなGUIページを作ります。
入出力 | 要素 | 内容 |
---|---|---|
入力a | input/text | 入力値 |
入力b | input/text | 入力値 |
入力c | input/text | 入力値 |
入力d | input/text | 入力値 |
入力e | select | 0〜3 |
出力a | div | ((入力ab)) |
出力b | div | [[入力dc]] |
出力c | div | 入力eが0なら入力ababdcdc,入力eが1なら入力abdcabdc,入力eが2なら入力dcabdcab,入力eが3なら入力dcdcabab |
● 設計
状態を依存グラフにしてみました。
● 実装
依存グラフを素直にコードに落としました。
-- elm test.elm import Graphics.Input.Field as Field import Graphics.Input as Input import String import Text import Window -- Signals and Inputs in_a = Input.input Field.noContent in_b = Input.input Field.noContent in_c = Input.input Field.noContent in_d = Input.input Field.noContent in_e : Input.Input (Int) in_e = Input.input 0 in_f_options : [(String, Int)] in_f_options = [ ("0. ababcdcd", 0) , ("1. abcdabcd", 1) , ("2. cdabcdab", 2) , ("3. cdcdabab", 3)] -- functions main : Signal Element main = printABCD <~ Window.dimensions ~ (selectABCD <~ (combineABCD <~ (wrapAB <~ (addAB <~ in_a.signal ~ in_b.signal)) ~ (wrapCD <~ (addCD <~ in_c.signal ~ in_d.signal))) ~ in_e.signal) printABCD : (Int, Int) -> (String, (String, String, String, String), (String, String, Field.Content, Field.Content), (String, String, Field.Content, Field.Content)) -> Element printABCD _ (select, (ababcdcd, abcdabcd, cdabcdab, cdcdabab), (w_ab, ab, in_a', in_b'), (w_cd, cd, in_c', in_d')) = flow down [ plainText "input a:", Field.field Field.defaultStyle in_a.handle identity "" in_a' , plainText "input b:", Field.field Field.defaultStyle in_b.handle identity "" in_b' , plainText "cache a: input a ++ input b", plainText ab , plainText "output a: (( ++ cache a ++ ))", plainText w_ab , plainText "input c:", Field.field Field.defaultStyle in_c.handle identity "" in_c' , plainText "input d:", Field.field Field.defaultStyle in_d.handle identity "" in_d' , plainText "cache b: input d ++ input c", plainText cd , plainText "output b: [[ ++ cache b ++ ]]:", plainText w_cd , plainText "input e:", Input.dropDown in_e.handle in_f_options , plainText "cache c(0):", plainText ababcdcd , plainText "cache c(1):", plainText abcdabcd , plainText "cache c(2):", plainText cdabcdab , plainText "cache c(3):", plainText cdcdabab , plainText "output c: cache c(input e)", plainText select ] selectABCD : ((String, String, String, String), (String, String, Field.Content, Field.Content), (String, String, Field.Content, Field.Content)) -> Int -> (String, (String, String, String, String), (String, String, Field.Content, Field.Content), (String, String, Field.Content, Field.Content)) selectABCD ((ababcdcd, abcdabcd, cdabcdab, cdcdabab), ab, cd) n = let s = case n of 0 -> ababcdcd 1 -> abcdabcd 2 -> cdabcdab _ -> cdcdabab in (s, (ababcdcd, abcdabcd, cdabcdab, cdcdabab), ab, cd) combineABCD : (String, String, Field.Content, Field.Content) -> (String, String, Field.Content, Field.Content) -> ((String, String, String, String), (String, String, Field.Content, Field.Content), (String, String, Field.Content, Field.Content)) combineABCD (w_ab, ab, a, b) (w_cd, cd, c, d) = ((ab++ab++cd++cd, ab++cd++ab++cd, cd++ab++cd++ab, cd++cd++ab++ab), (w_ab, ab, a, b), (w_cd, cd, c, d)) addAB : Field.Content -> Field.Content -> (String, Field.Content, Field.Content) addAB a b = (a.string ++ b.string, a, b) wrapAB : (String, Field.Content, Field.Content) -> (String, String, Field.Content, Field.Content) wrapAB (ab, a, b) = ("((" ++ ab ++ "))", ab, a, b) addCD : Field.Content -> Field.Content -> (String, Field.Content, Field.Content) addCD c d = (d.string ++ c.string, c, d) wrapCD : (String, Field.Content, Field.Content) -> (String, String, Field.Content, Field.Content) wrapCD (cd, c, d) = ("[[" ++ cd ++ "]]", cd, c, d)
動いたっぽいです。
ここら辺を参考にしました。
■ 感想
FRP/Elmともにいまいち理解が正しいのか分かりません。もう少し調査が必要な気がします。