これは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:エラー処理をサボれない
Maybe
やResult
を使って、エラー処理を厳格に行えます。
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にはMaybe
、Result
、Http.Response
などのunion typeがあるおかげで、エラー処理を安全に行えるのですが・・・case文がネストしまくって大変です。
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 」と、コードレビューで却下されても仕方ない感じのコードをElmでは書かざるをえません。
また、標準ライブラリのSignal
Maybe
モジュールも、「Signal (Maybe a)
からNothing
ではない値を取り出して返すSignal a
」のような関数が無いなど、細かい関数も不足しています。
悪い所3:レイアウト・スタイルライブラリの不足
ElmはHTML/CSSよりずっと直感的にレイアウト・スタイルを書けるのですが、やはりまだ機能不足です。
例えば、
- CSSの"border"相当のものが無い(containerを2つ重ねて無理やりborderを表現することはできます)。
plainText
をcontainer
で囲んでも、中の文字列は改行されない(自動で改行させる方法がない?自分で改行位置で分割してText.Line
などを使えば可能)。- 「画像を400x400に収まるように縦横比率を変えずにリサイズ」などができない(そもそも画像サイズを取得できない。
Port
を使えば無理やりできなくもないかもしれないが・・・)
一般的に、基本的なことはElmの方がAngularJSより簡単に出来るのですが、上に上げたような事を実装しようとするとElmでは不可能ではないものの多くの労力を割いて、細かいところまで実装しなくてはなりません。
「よくある処理」「80%のケースを20%の労力でカバーできるデフォルトの選択肢」がもっと用意されればいいな、と思いました。
あとがき
Elm Advent Calendar 2014にはまだ十分な余裕がございます。ふるってご参加ください。