AngularJSのチュートリアルを移植して感じた、Elmの3つの良い点と不満点

これはElm Advent Calendar 2014の14日目の記事です。

AngularJSのチュートリアルをElmで再実装してみました!

doloopwhile/elm-phonecat · GitHub

まだ、本体のソースコードだけで、解説は何もないのですが・・・。

書いていて、気づいた事を、良い点・不満点それぞれ3つずつ書いていきます。

良い点1:必要な機能が十分に実装されている

Elm v0.14ではJson.Decoderが追加され、JSONのパースを実用的な記述量で実装できるようになりました。

私にとってはこれがAngularJSのチュートリアルをElmで再実装する上での「最後のピース」でした。

もちろん「ウチの業務アプリをElmで再実装してよ」と言われると「いやAngularJSの方が・・・」となります。まだElmには荒削りなところもあります。

しかし、「オモチャ」と言われる段階は脱したという印象です。

良い点2:直感的

Elmは「やりたいこと」と「解決法」がすぐ近くにある感じです。AngularJSのように新しいモジュールを勉強する回り道をしなくても、すぐに解決出来るのです。

例えば、AngularJS版のチュートリアルでは「True/Falseの代わりに"✓✗"を表示する」のために、Step-9を丸々ngFilterの説明に使っています。

一方Elmでは、

checkmark x = if x then plainText "\x2713" else plainText "\x2718"

の一行だけで用が足ります。

良い点3:エラー処理をサボれない

MaybeResultを使って、エラー処理を厳格に行えます。

JavaScriptだとNULLチェックなどは「if != null、if != null はぁ〜面倒くさいなあ」となってしまいがちですが、ElmではMaybeのチェックをしないとそもそもコンパイルが通らないので、ものぐさな私でも安全なコードを強制的に書くことになります。

まぁ、「厳格」はデメリットでもあります(JSONのフィールドを全列挙しないとDecoderが作れないとか)。

不満点1:Signalが不便(特に、ページの階層構造を作りづらい)

一方、ElmはまだSignalが使いづらいと感じました。

今回作ったのはシングルページアプリケーション(SPA)でしたが、AngularJSの場合は、

  • 実際にページを描画するTemplate/Controller
  • URLからTemplate/Controllerを選択するngRoute

という仕組みになっています。ngRoute側ではページの描画の仕方を知る必要はありません。 また、各ページのTemplate同士(例えば、トップページのTemplateと、詳細ページのTemplate)は互いの事を知る必要がありません。「関心の分離」ができる、ナイスな仕組みです。

しかし、残念ながら、現在のElmではngRouteの真似はできないようです。

-- 注:このコードは実際のElmではありません。

-- URLのハッシュによって、ページを切り替える関数
route : Location -> Signal Element
route location = 
  if | startsWith("#/phones", location.hash) -> phoneView location
     | otherwise -> indexView

-- トップページを描画
indexView : Signal Element
indexView =
  indexPageImpl <~ Time.fps 30 ~ Mouse.clicks

-- カタログの個別ページの描画
phoneView : Location -> Signal Element
phoneView location =
  let
    api = Http.sendGet ("/api/phones/" ++ String.dropLeft(8, location.hash))
  in
    phonePageImpl <~ Time.fps 30 ~ Mouse.position ~ api

-- さて、main関数の型は?
main = route <~ location

main関数はElement型かSignal Element型でなくてはならないからです。そのためmain関数は「全ページの描画とルーティングに必要な全部のSignalを<~ ~で連結する」ことになりがちです。

不満点2:union typeの処理が煩雑

ElmにはMaybeResultHttp.Responseなどのunion typeがあるおかげで、エラー処理を安全に行えるのですが・・・case文がネストしまくって大変です。

今回のチュートリアルでは、JSON APIのレスポンスを、

phonesResp : Http.Response (Result String (List Phone))

という型にしました。

実際にその値を処理している部分はこちら

case phonesResp of
  Http.Waiting ->
    Text.plainText "waiting"
  Http.Failure code text ->
    Text.plainText ("failed with " ++ (toString code) ++ " " ++ text)
  Http.Success resultPhones ->
    case resultPhones of
      Err error ->
        Text.plainText error
      Ok phones ->
        indexElements q sortPhones phones

JavaScriptだったら「早期リターンに書きなおすこと -1 f:id:doloopwhile:20141215023108p:plain」と、コードレビューで却下されても仕方ない感じのコードをElmでは書かざるをえません。

また、標準ライブラリのSignal Maybeモジュールも、「Signal (Maybe a)からNothingではない値を取り出して返すSignal a」のような関数が無いなど、細かい関数も不足しています。

悪い所3:レイアウト・スタイルライブラリの不足

ElmはHTML/CSSよりずっと直感的にレイアウト・スタイルを書けるのですが、やはりまだ機能不足です。

例えば、

  • CSSの"border"相当のものが無い(containerを2つ重ねて無理やりborderを表現することはできます)。
  • plainTextcontainerで囲んでも、中の文字列は改行されない(自動で改行させる方法がない?自分で改行位置で分割してText.Lineなどを使えば可能)。
  • 「画像を400x400に収まるように縦横比率を変えずにリサイズ」などができない(そもそも画像サイズを取得できない。Portを使えば無理やりできなくもないかもしれないが・・・)

一般的に、基本的なことはElmの方がAngularJSより簡単に出来るのですが、上に上げたような事を実装しようとするとElmでは不可能ではないものの多くの労力を割いて、細かいところまで実装しなくてはなりません。

「よくある処理」「80%のケースを20%の労力でカバーできるデフォルトの選択肢」がもっと用意されればいいな、と思いました。

f:id:doloopwhile:20141215024400j:plain

あとがき

Elm Advent Calendar 2014にはまだ十分な余裕がございます。ふるってご参加ください。