uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

elmでやってみるシリーズ3: xeyesっぽい目が動く

elmでやってみるシリーズ3: xeyesっぽい目が動く。

import Mouse

-- 目の輪郭を書く
eyeframe x y = move (x,y) <| outlined { defaultLine | width <- 5 } <| oval 40 50

-- 目玉を書く
eyeball x y = move (x,y) <| filled black <| circle 5

-- 目(=目玉+目の輪郭)を一個分書く。tは目玉の見ている方向。
eye r x y t = [eyeframe x y, eyeball ((r * (cos t))+x) ((r * (sin t))+y)]

-- 目を2つ書く
eyes t = collage 200 200 <| (eye 15 -20 0 t)++(eye 15 20 0 t)

-- 「目の座標位置を計算する(=「マウス座標の原点から見た角度」をatanで求める)」という純粋関数にマウス座標をリフト適用。シグナル化された目玉の角度が返る。
mouseDirec = let f x y = (atan2 (100-(toFloat y)) ((toFloat x)-100))
           in f <~ Mouse.x ~ Mouse.y

-- 目を2つ書く、という純粋関数に、シグナル化された目玉の角度(mouseDirec)をリフト適用する。
main = eyes <~ mouseDirec


この簡潔さよ! ブラボー。Elm ブラボー。(さぼってますけどね。)

ブラウザ内で編集したり実行したい場合はこちらからどうぞ。

気づいたこと

  • SignalはElmのFRPのキモである。Signalを制するものはElmを制する。
  • mainの直下にSignalを使うReactive Codeを固めて、他はなるべくピュアにしたい、と思うじゃない。でもそれはたぶん無理な相談である。いや、やれるところはそうすれば良いと思うけど、「Signalを返す関数」はElmプログラミングにとって中核的であり最重要。それをどう組むべきかという問題からは逃げられない。
  • 「なるべくピュアにしたい」という理由は、liftの適用は演算子<~,~があろうともやっぱり可読性に難があるから。今のElmにはセクションがないし、演算子関数をliftすると演算子っぽくなくなるし、flip駆使しても限界がある。もっと言えばSignalはモナドではないし*1、仮にモナドであってもElmにDo記法は無い。で、考えたのですが、ベストプラクティスとしてのコーディングパターンは、上でもしているように、lift一発でできないところは以下のように関数ごとに純粋部とリアクテイブ部に分けることではないかと今のところ思っております*2。なお今のElmはwhere句は使えません。
func = let f x y ... = 純粋コード
      in  f <~ Signal X ~ Singal Y ... -- 必要なSignalを使用するリアクティブコード

関連エントリ


「Elmでやってみる」シリーズのまとめエントリ - uehaj's blog

*1:Signalがモナドでなくアプリカティブなのは理由があって、「SignalのSignal」を禁止するためだそうな。つまりSignalにjoinは定義できない・してはならない。bind/flatMapも然り。

*2:fは無名関数でも良いかもしれない。letのセマンティクスが無名関数とその適用に差があるのか、たとえばElmにlet多相があるかどうかは不明。