Elmでライフゲームを作った話

前置き

あるときElmという言語に興味を抱いた。
それはJavascriptにコンパイルできる関数型プログラミング言語で、静的型の検査が強力で、
GUIアプリケーションを極めて簡単に書けるのだという。

ちょうどJSで動くWebアプリケーションを作ってみたいと思っていたので、
ならば試してみよう、TODOアプリなどでは面白みがないので個人的に多少興味のあるライフゲームでも作ってみよう、ということになったのである。

バージョン

できたもの

https://game-of-life-elm.netlify.com

ソースコード: https://github.com/wolf-dog/elm-game-of-life

特徴:

感動した点

多くのエラーをコンパイル時にチェックしてくれる

コンパイル時のエラーチェックが強力で、この段階でほとんどのバグを発見できる。
公式に “No Runtime Exceptions” と謳っている通りである。

関数の引数などの型チェックはもちろんのこと、 カスタム型をパターンマッチする際に選択肢が網羅されていないことをチェックしてくれたりもする。
コンパイルさえ通せばまずエラーのないプログラムが作れる、というのは非常に良い体験だった。

エラーメッセージも以下のような感じで親切なのが嬉しい。

type CellShape
    = Square
    | Squircle
    | Circle

cellShapeToString : CellShape -> String
cellShapeToString shape =
    case shape of
        Square ->
            "Square"

        Squircle ->
            "Squircle"

This `case` does not have branches for all possibilities:
 8|>    case shape of
 9|>        Square ->
10|>            "Square"
11|>
12|>        Squircle ->
13|>            "Squircle"

Missing possibilities include:
    Circle

公式ガイドが充実している

https://guide.elm-lang.org/
公式ガイドでは実際に使いそうな機能のコードを通じて言語機能を学んでいくことができる。
解説も丁寧なので、これを順にこなしていけばとりあえず単純なアプリケーションは作れるようになる。

やや版が古いが日本語版もある。
https://guide.elm-lang.jp/

ちなみにガイドで解説されていない機能について知りたい場合はパッケージドキュメントに頼ることになる。
https://package.elm-lang.org/
なお算術演算子や比較演算子などもElmでは関数なのでここにまとめてあったりする(これに気づきにくい)。
https://package.elm-lang.org/packages/elm/core/latest/Basics#(+)

言語がフレームワークのようなものと一体になっている

Modelupdateviewのような型や関数が言語機能に組み込まれており、
それらの内容を実装すればモデルと画面表示の相互更新などを自動で行ってくれる。

通常の言語に入門した場合、実用的なアプリケーションを作るためには
フレームワークを選定して学んで、という手順が必要になることが多いが、
Elmではそれが言語の学習と一体化していて迷いがない。

こういう潔い割り切りができるのは強い。
言語の目的をGUIアプリケーションの作成に絞っているからこそだろう。

もちろんこれはアーキテクチャの選択肢がないとも言えるが、
ElmはElm Architectureを実現する言語という位置づけでもあるので、
そこはそういうものだと思うしかないだろう。

苦労した点

作法が結構独特

文法が結構独特で慣れるまでに時間がかかった。
ただしこれは他の関数型プログラミング言語を通ってきていれば馴染みやすいという噂も聞く。

また1ファイルをいくらでも長くしても良い、むしろモジュール化したくなる気持ちを抑えよ、というのがElm的な考えかたらしい。
実際update関数などは普通に書いているとどんどん長大になっていくし分割もしづらいのだが、それで良いのだ、とのこと。
無駄に分割しすぎるべきではない、というのは確かにその通りだが、
他の言語では「1ファイルは短いほうが良い」とされがちなので戸惑いはある。
また複数人開発ではコンフリクトが起きやすかったりdiffが一見で分かりづらかったりしそうだな、とも思う。

乱数生成が理解し難い

通常の関数では乱数を生成することはできず、Cmdを通じて行う必要がある。

以下の例はhttps://guide.elm-lang.org/effects/random.htmlより一部抜粋。

type alias Model =
  { dieFace : Int
  }


type Msg
  = Roll
  | NewFace Int


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Roll ->
      ( model
      , Random.generate NewFace (Random.int 1 6)
      )

    NewFace newFace ->
      ( Model newFace
      , Cmd.none
      )

モデルのdieFaceにランダムな6面ダイスの出目を設定するだけでこれだけの実装が必要になる。
自分のような初心者はそもそもCmdGeneratorの何たるかが曖昧にしか理解できていなかったりするので、
これをベースに望みの乱数処理を作る、と考えるだけで狂いそうになってくる。

コンパイル時に予測不可能な要素を導入したくない、という思想なのは分かるのだが、
それにしても中々理解するのが難しくなってしまっているように感じた。

HTMLの記法がつらい

ElmではHTMLのタグも関数として表現され、それは以下のようになる。

Html.button
  [ Html.Attributes.class "btn btn-info"
  , Html.Events.onClick Clear
  ]
  [ Html.text "Clear" ]

Html.[タグ]関数の第1引数がattribute等のList、第2引数が子要素のListという形式である。
ということはネストするとこうなる。

Html.div [ Html.Attributes.class "nav" ]
  [ Html.h1 [] [ Html.text "Game of Life" ]
  , Html.div [ Html.Attributes.class "menu btn-group" ]
      [ Html.button
          [ Html.Attributes.class "btn btn-info"
          , Html.Events.onClick Clear
          ]
          [ Html.text "Clear" ]
      ]
  ]

正直かなり見づらいし、書くのはそれ以上につらい。
一方で関数であるということは好きにヘルパー関数を定義して分解できる、ということでもあるので、
積極的にそうしていけ、というお告げなのかもしれない。

機能にやや不足がある

例えばselectタグを入力として使うための関数や、マウスクリック時の座標を取る関数がない。
自分で実装すれば良いとはいえ、これは公式に提供されていても良いのでは、と思うことも多い。

非公式パッケージにあったりはするので、そのうち取り込まれるものもあるかもしれない。

個人的な感想まとめ

馴染みづらいところもあるが、ほぼElmの知識だけで簡単にGUIアプリケーションを作れるというのはとても良い。
今後もWebブラウザで動く小品を作りたいときにはどんどん使っていきたい。

余談

完成後にふとGitHub等で他のElmによるライフゲーム作例を検索してみたところ、出るわ出るわ。
言語の入門としてライフゲームを作ってみる、というのは思った以上に一般的な発想だったようだ。

セルの初期配置ファイルをインポートできるものや、オプションで世界端を折り返しと見なせるもの、
セルの生息範囲が広がると自動的に世界も広がるものなど、思いもつかなかったようなものが多々あって非常に面白かった。