Elmでやってみるシリーズ16: マウスストーカーを実装する
「リアクティブプログラミングの技術を用いてマウスストーカーを実装する - はこべブログ ♨」という記事があり、興味深いのでElmのリアクティブプログラミングで似たようなことをやってみました。
全画面表示はこちらから。
コードは以下で、プロジェクト全体はこちらにあります。
import Text import Window import Time import Mouse import List import Signal import Graphics.Element(..) import Graphics.Collage(..) import Color(..) import Signal(Signal,(<~),(~)) import AnimationFrame -- マウスの座標をCollageの座標に変換するいつもの関数 mousePos : Int -> Int -> Int -> Int -> (Float, Float) mousePos x y w h = (toFloat x-(toFloat w/2) , -(toFloat y)+(toFloat h/2)) -- ★の表示 star : Int-> Int -> (Int, Int) -> Form star w h (x, y) = Text.fromString "★" |> Text.color orange |> Text.centered |> toForm |> move (mousePos x y w h) -- ビューの定義 view : List (Int, Int) -> (Int, Int) -> Element view posList (w,h) = collage w h (List.map (star w h) posList) -- ★の座標のリストのSignalを作る stars : Signal(List (Int, Int)) stars = let trace = Time.delay 100 -- 100ms遅延を与えたSignalを生成 p1 = Signal.sampleOn AnimationFrame.frame Mouse.position -- 最初はマウス座標を追う p2 = trace p1 -- 以降、一個前の座標を追うようにする p3 = trace p2 p4 = trace p3 p5 = trace p4 p6 = trace p5 p7 = trace p6 p8 = trace p7 p9 = trace p8 p10 = trace p9 p11 = trace p10 p12 = trace p11 p13 = trace p12 p14 = trace p13 in (\a b c d e f g h i j k l m n -> [a, b, c, d, e, f, g, h, i, j, k, l, m, n]) <~ p1 ~ p2 ~ p3 ~ p4 ~ p5 ~ p6 ~ p7 ~ p8 ~ p9 ~ p10 ~ p11 ~ p12 ~ p13 ~ p14 -- 表示関数viewをliftして★の座標をあてがう main : Signal Element main = view <~ stars ~ Window.dimensions
非常に簡潔です。
説明と注意点など
- Mouse.positionおよびTime.delayが、シグナル(beacon.jsでいうEventStreamに相当)を生成しています。ちなみに次期Elm 0.15では、Signalは名称変更(Stream/Varyingに分割)が検討されているようです
- 上記コードではマウス位置のサンプリング間隔を効率化するために AnimationFrameというコミュニティパッケージを使用しています。なので他のパッケージを使用する機能がないtry-elmやshare-elmでは実行できません。sampleOnをfps 60とかですれば、もしくはsampleOnを使用せず直接Mouse.positionを使用すれば、AnimationFrameへの依存は除去できます。
- 制限としては、ElmのCanvasベースのAPIであるGraphics.Collageを用いているため、★がifameの枠外まで追随することはありません。Nativeやportを用いてJSと連携すれば、真のマウスストーカーができるかもしれませんが試してはおりません。ちなみに次期版0.15ではJSとの連携能力が大きく変わるようです。
- 上記コードでp1〜p14を列挙していますが、例えばmapを使用してリストを生成していないのは、Signalは動的に生成できないからです。Elmでは、すべてのSignalの値の依存関係に対応する「Signalグラフ」というものがコンパイル時に静的に決定されます。そのこともあり、不定件数の「Signalのリスト(List Signal a)」というものは作成できたとしても、(Signal (List a))に変換できないのです。sequence :: [m a] -> m [a] が欲しいところですが、そういう関数はなく、定義することも型制約*1で意図的に禁止されています。
余談
もうすぐ出るらしいElm 0.15は、非常に楽しみです。
参考リンク
Elmシリーズの他のエントリはこちら。