Elmを使ってみた時のメモ (その1)
Elmとは
- 公式サイト
- Functional Reaticve 言語
- インタラクティブなアプリケーションのための言語
- HTML + CSS + JavaScript にコンパイルされる
- オンラインで簡単に試すことができる
とりあえず使ってみた
Hello, World.
Elmはサンプルプログラムが非常に充実している。とりあえずHello, World.プログラムを作ってみた。
import Text(plainText) main = plainText "Hello, World."
オンライン環境でコンパイルしたら、Hello, World.と表示された。
plainText
は以下の型の関数でText
モジュールに定義されている。
plainText : String -> Element
型を明記する
以下のようにプログラムの型を明示することもできる。
import Graphics.Element(Element) import Text(plainText) main : Element main = plainText "Hello, World."
式の計算をする
import Graphics.Element(Element) import Text(..) main : Element main = plainText <| toString <| 2 + 3 * 4
toString
はBasics
モジュールの関数で任意の値を文字列に変換する。
toString : a -> String
(<|)
は関数適用の演算子で、Basics
モジュールで定義されている。
(<|) : (a -> b) -> a -> b
ここら辺はF#と同じ。|>
、<<
、>>
も定義されている。
Basics
モジュールはデフォルトでインポートされるので明示的なインポートは不要。
デフォルトでは以下がインポートされるらしい。
import Basics (..) import List ( List ) import Maybe ( Maybe( Just, Nothing ) ) import Result ( Result( Ok, Err ) ) import Signal ( Signal )
関数を定義する
階乗計算
まずは定番の階乗計算から。
import Graphics.Element(Element) import Text(..) main : Element main = plainText <| "fact 5 = " ++ toString (fact 5) fact : Int -> Int fact n = if | n == 0 -> 1 | otherwise -> n * fact (n - 1)
if
のthen
の部分に->
を使う所にとても親近感がわく。良くできたシンタックスだと思う。
実行すると、
fact 5 = 120
と表示される。
階乗の表(1)
次は階乗の表を表示してみたいと思う。
import Graphics.Element(..) import Text(..) import List(..) main : Element main = flow down <| map factElement [1..10] factElement : Int -> Element factElement n = plainText <| "fact " ++ toString n ++ " = " ++ toString (fact n) fact : Int -> Int fact n = if | n == 0 -> 1 | otherwise -> n * fact (n - 1)
Graphics.Element
モジュールのflow
関数でElement
を配置できるらしいのでそれを使った。
flow : Direction -> List Element -> Element
階乗の表(2)
せっかくなので、=
のところを揃えて表示させたいと思う。ついでに等幅フォントにしてみた。
import Graphics.Element(..) import Text(..) import List(..) main : Element main = let range = [1..10] in flow right <| map (flow down) <| map (\f -> map f range) [expr, equal, value] expr : Int -> Element expr n = container 60 18 midRight << leftAligned << monospace << fromString <| "fact " ++ toString n equal : Int -> Element equal n = container 30 18 middle << leftAligned << monospace <| fromString "=" value : Int -> Element value n = container 60 18 midLeft << asText <| fact n makeContainer : Int -> Position -> Element -> Element makeContainer w pos elem = container w 12 pos elem fact : Int -> Int fact n = if | n == 0 -> 1 | otherwise -> n * fact (n - 1)
以下のようにそれらしい表示が出た。
マウス座標の表示
マウスのX座標の値を表示するプログラムを作ってみた。
import Graphics.Element(..) import Text(..) import Signal import Mouse main : Signal Element main = Signal.map asText Mouse.x
main
の型がこれまでのElement
からSignal Element
に変わった。
Signal
というのは要素の無限列というイメージだろうか。
例えばMouse
モジュールの値x
は、
x : Signal Int
という型を持っている。
これをSignal
モジュールのmap
関数で、Signal Element
にすると、リアクティブなプログラムができるらしい。
map : (a -> result) -> Signal a -> Signal result
なんだ、意外と簡単ではないかと思った。 要はこれを組み合わせてプログラミングしていけば良いということなのだろう。
時間を扱ってみた
とりあえず1秒に1ずつカウントするプログラムを作ってみた。
import Graphics.Element(..) import Text(..) import Signal import Time(..) main : Signal Element main = Signal.map asText << Signal.foldp update initial <| every second initial : Time initial = 0 update : Time -> Time -> Time update v st = st + 1
状態を扱うためには、Signal
モジュールのfoldp
関数を使うらしい。
ボールが移動するプログラム
左右に移動する
import Graphics.Element(..) import Graphics.Collage(..) import Color(..) import Signal(..) import Time(..) main = draw <~ foldp update init (fps 30) draw st = collage 300 200 [ moveX st.x (filled blue (circle 10)), outlined (solid black) (rect 300 200) ] init = { x = 0, vx = 5 } update v st = let new_x = st.x + st.vx in { st | x <- new_x, vx <- if new_x > 150 || new_x < -150 then negate st.vx else st.vx }
座標系を理解するのに少し時間がかかった。レコードを初めて使ってみた。
ウインドウ内をボールが飛び跳ねる
import Graphics.Element(..) import Graphics.Collage(..) import Color(..) import Signal(..) import Time(..) import Window(..) type Event = Tick | Resize Int Int type alias State = { x : Float, y : Float, vx : Float, vy : Float, w : Int, h : Int } main : Signal Element main = map draw <| foldp update init event event : Signal Event event = delay 1 (sampleOn tick resize) `merge` tick tick : Signal Event tick = always Tick <~ fps 30 resize : Signal Event resize = uncurry Resize <~ dimensions init : State init = { x = 0, y = 0, vx = 5, vy = 5, w = 500, h = 500 } draw : State -> Element draw st = collage st.w st.h [ move (st.x, st.y) (filled blue (circle 10)), outlined (solid black) (rect (toFloat st.w) (toFloat st.h)) ] update : Event -> State -> State update ev st = case ev of Tick -> let new_x = st.x + st.vx new_y = st.y + st.vy in { st | x <- new_x, y <- new_y, vx <- new_v st.x st.vx st.w, vy <- new_v st.y st.vy st.h } Resize w h -> { st | w <- w, h <- h } new_v : Float -> Float -> Int -> Float new_v x vx w = if | x > toFloat w / 2 -> -(abs vx) | x < -(toFloat w / 2) -> abs vx | otherwise -> vx
ウインドウの初期サイズを取得するところで非常に苦労した。
時間0のウインドウサイズの通知と同時にfps
のイベントも発生するため、
片方を遅延させる必要があった。
もっとエレガントな解法があるのかもしれないが、現時点では分からない。
今日はここまでにしよう。