こんにちは。Elm2(完全版) Advent Calendar 2018 - Qiitaの12月11日の担当の@gaaamiiです。
技術的な内容ではない身の上話が多めです。ごめんなさい。
3行で
- Elmをなんとなく触り始めて
- 仕事でも使えるようになった
- 良いことも、難しいこともあった
経緯
以下、Elmをなんとなく触り始めてから、仕事で使うようになるまでの経緯を書きました。
趣味でMarkdownメモ帳の開発
昔々、ブラウザで動くいい感じのMarkdownメモ帳が欲しくて、作ったものがありました。
当時はJavaScript MVCのフレームワークといえばBackbone.jsかangular.jsかという感じでした。
ビルドツールにはgulpを、パッケージ管理にbowerを使っていたりして、時代を感じます。
なんとか最低限使えるものにはしたものの、2015年春にデータの保存先として使っていたDropbox Datastore APIの廃止が発表され、同時に私自身も就職して忙しくなったので、これに手を入れることはほぼなくなりました。しかしその後もなんとなく気になっていて、何かしらのいけてるライブラリで作り直すなりしたい、という気持ちだけは持っていました。
- gaaamiiさんのツイート: “Riot.js使ったほうが話題になりそうだからRiot.js使うか https://t.co/ZDamSINQ9v”
- gaaamiiさんのツイート: “Elmで https://t.co/XN3SkA3f5N のリニューアルにチャレンジしてるけどElmさっぱりわからない”
そんなことをふわふわ考えている中で、なぜかVueやAngularには惹かれず、Riot.jsはドキュメントちょっとは眺めたのですが、趣味開発の時間を取らないうちに興味を失ってしまいました。また、ReactやReduxがいけてるのじゃないかと思っていたものの、仕事でReactを使っているのでなにか違うものを使ったほうが面白いんじゃないかと思い、こちらも使いませんでした。
そういう感じで、大した志もなく、強いて書くなら以下のような理由でElmを触り始めました。
- Reduxの元ネタだから、Elmを仕事で使うことはなくてもReduxやるときに役に立ちそう
- Clojureを一瞬触ろうと思った時期があったけど結局勉強が続かなくて、しかし何かしら関数プログラミングっぽい言語を一つは身につけておきたいと思っていた
- なんでそう思っていたかというのはおそらく「ハッカーになるには」というエッセイのこの辺の文章)の影響。
作っているものは https://nekobito.github.io にあり、その開発メモはだいたいScrapboxのNekobito - きまべんに書いてあります。手探りで学びながら、コードを書いていました。
仕事での導入チャンス
昨年の今頃、仕事ではReactを書いていましたが、たまたま社内の週1の勉強会でElmの発表をする機会が何度かありました。その当時から仕事で使うぞという気持ちがあったわけではなく趣味の範疇におさめておくつもりだったのですが、社内では「なんかgaaamiiがElmやっているらしいぞ」というのが知られました。
そうこうしている間に、今年の夏、仕事で新たにちょっとしたサブシステムが必要になり、その小さなSPAのためのウェブフロントエンドの言語として、Elmを採用できるんじゃないかという可能性が出てきました。
自社のプロダクトのほとんどは、サーバーサイドはRuby on Railsで作られており、ウェブフロントエンドはJavaScriptかTypeScriptです。Elmでウェブのフロントエンドを書くことはけっこう挑戦的でしたが、会社としても他の言語に手を伸ばしていきたいという話もあり、CTOとチームリーダーから許しを頂き、Elmでの実装に至りました。
Elmについて学んだこといろいろ
Elmを書くと決めて、いろいろ学ぶ必要がありました。今でもすべて理解ができているとは言い難いので、改めて調べながら書いています。基本的には、Introduction · An Introduction to Elmを上から読んでいけば良いと思います。
- TEA(The Elm Architecture)
- 関数シグネチャ(型)の読み方
- typeについて
- JSとのつなぎ方
TEA(The Elm Architecture)
TEAについては、もともとReduxについてある程度知っていたので、それとほぼ同じものとして理解できました。Reduxではactionをdispatchするとreducerで定義された変更が反映された新しいstateが得られる。Elmでは update : Msg -> Model
で、Msgがactionに相当します。viewはmodelの値に応じたHTMLを返す関数 (view : Model -> Html msg
)です。
関数シグネチャ(型)の読み方
Elmを使うためには、関数シグネチャの読み方について知る必要がありました。Elmでは、モジュールとして整理された関数があり、その関数に決められた型の引数を渡して、出力を得るだけですが、学びはじめた当初、すべての関数が一つの引数しか受け取らないということに、少し戸惑いました。以下のブログ記事に書きました。
typeについて
Elmでは、あらゆるものが関数です。これはシンプルでわかりやすい。関数を定義するときは fuga = "fuga"
とかhoge = (\_ -> "hoge")
みたいな形で、名前をつけて=
で束縛すればよく、その名前を打ち込めば実行できる。
ただ、typeという特別なキーワードがあります。 type Hoge = Foo String
みたいなのを書きます。type
ってなんなんだ、という話になります。
type
の使い所は、Type Aliasと、Custom Typesの二種類です。
Type Alias
これは特に難しくありません。自分でデータ型を定義するだけです。
type alias Hoge = { id : String }
すると、関数シグネチャを書くときに使えます。
outputHoge : Hoge
outputHoge hoge =
hoge
普通ですね。ただ面白いことに、この型エイリアスも関数になっています。それをElmのREPL上で見ておきます。
上で定義した、Hogeだけで出力を見ます。
Hoge
<function> : String -> Hoge
String -> Hoge
な関数です。ということは…?
> Hoge "hoge"
{ id = "hoge" } : Hoge
Stringを渡すとレコードが返ってきました。
じゃあ、レコードのフィールドがたくさんあると…?
> type alias Fuga = { id : String, name : String }
> Fuga
<function> : String -> String -> Fuga
String -> String -> Fuga
です。ということは….?
> Fuga "foo" "bar"
{ id = "foo", name = "bar" } : Fuga
おお〜。フィールドが2つでも、順にフィールドに文字列が入ったレコードが返ってきました。
Custom Type
こちらはもう少し難しかったです。
Note: Custom types used to be referred to as “union types” in Elm. Names from other communities include tagged unions and ADTs.
他の言語では、ユニオン型とかタグとか、Algebraic data typeとか呼ばれるみたいです。なるほどよくわからない。
Elmで真っ先に目にするのは、Msg
という型ではないでしょうか。TEAで、update関数のパターンマッチをするためのパターンが、これを使って表現されています。
単純なのはこういう形です。
type Msg = OnClick | OnInput
update : Msg -> Model -> (Cmd Msg)
update msg model =
case msg of
OnClick -> ({ model | clicked = True }, Cmd.none)
OnInput -> ({ model | input = True }, Cmd.none)
起きうるイベントをtype
で定義しておいて、それに応じて次の状態(model)をつくっています。
しかし、実際のアプリケーションでは、なにかイベントが起きたときに、そのイベントに紐づくデータを使うことがほとんどです。OnInput
であれば、ユーザーが入力しているその入力フォームの値です(そもそも上のままHtml.Events.onInput OnInput
と渡そうとすると型が合わなくてエラーになる)。
そういう場合は、このようにします。
- type Msg = OnClick | OnInput
+ type Msg = OnClick | OnInput String
update : Msg -> Model -> (Cmd Msg)
update msg model =
case msg of
OnClick -> ({ model | clicked = True }, Cmd.none)
- OnInput -> ({ model | input = True }, Cmd.none)
+ OnInput inputValue -> ({ model | inputValue = inputValue }, Cmd.none)
このとき、msgはOnInput "text"
みたいなものになっています。OnInputというカスタム型に、Stringのデータがくっついています。わかりづらいのでREPLで示すと、こういうことになっています。
> type Msg = OnInput String
> OnInput
<function> : String -> Msg
> OnInput "text"
OnInput "text" : Msg
> case (OnInput "text") of OnInput text -> "ペイロードは" ++ text ++ "です"
"ペイロードはtextです" : String
こういうことをREPLで評価しながら、ふむふむそういうことかというのが理解できました。
JSとのつなぎ方
Elmはまだライブラリが十分に充実しているとはいえず、たとえばlocalStorageにつなごうと思ったらJSでやる必要がありました。ElmをJSと共に使うには、portsという仕組みを使って、Elm側で扱う値やイベントを、JSで書いたプログラムとやりとりをするような書き方をします。
Elm実際どうなのか
趣味で使い始めて、小規模とはいえ仕事でも使うようになって、実際Elmの使い勝手はどうなのか。良さと難しさについて書きます。
良さ
関数だけ
ややこしい言語仕様はなく、全部関数なのでシンプルです。
No Runtime Error
おかしいところがあればコンパイルエラーになります。
elm-format
elm-formatというツールのおかげで、lintとかいろいろ設定しないとコード書けないみたいな面倒さがない。
Debugger
単純で使いやすくて好きです。その時点に戻ってChromeのDevツール開いてHTMLやらスタイルを確認したりというのをよくやります。
REPL
上で書いてきたように、たいていのことはREPLで確認できます。これは自分の書いている実装についてもそうです。自分のプロジェクトのディレクトリでelm repl
を打ち込み、もし関数がMyModule
というモジュールのmyFunc
という名前なら、import MyModule exposing (myFunc)
と打てば、その関数をREPL上で実行できます。
難しさ
Elm 0.19にする対応
こちらに書いた通りです。
小規模アプリでもそこそこ大変でした。
Json.Decodeつらい問題
APIとつないだときに、そのレスポンスをElmのデータ型にするというときに Json.Decode
というモジュールを使います。JSONはただの文字列なので、こういう処理はどの言語でも必要になると思います。こういう形になります。
import Json.Decode as Decode
userDecoder : Decode.Decoder User
userDecoder =
Decode.map2 User
(Decode.field "id" Decode.string)
(Decode.field "name" Decode.string)
-- > Decode.decodeString userDecoder "{\"id\":\"hgoehoge\",\"name\":\"fugafuga\"}"
-- Ok { id = "hgoehoge", name = "fugafuga" }
レスポンスの型の数だけ、このデコーダーを書かないといけないのがなかなかつらいです。現状はどうにかならないものかという感じですが、実はなにか良い方法があるのかもしれない。
モジュール分割の方針
Elmでは、JSのようにモジュールを細かに分割することが勧められていません。Reactのように、コンポーネントとして分割するのもよろしくないようです。
とはいえ、Main.elm
にすべての関数を入れてしまっていいのか?というと、そうではないと思います。
改めてモジュールを分ける意味を考え直す必要がありました。
この辺も、どう考えたらいいのか難しいと感じています。
学習コスト
自分しかやっていない言語を仕事で導入するというのは、かなり自分勝手で悪いことをしてしまったんじゃないか…という気持ちになることもあります。学習コストばかり高いものを無責任に周りに強いてるんじゃないかと。
しかし、Elmには上に書いたような良さがあるので、新しいことを学んででも使う価値はあるはず。そう思ってやっています。
それまではElmを楽しむだけだったのですが、仕事で使っている以上はもっと理解を深めて、Elmを使いたくなるようなメリットを社内外へ伝えていかなくてはいけません。難しいですが、これも頑張りたいところです。
まとめ
最初は、個人開発でなんか新しいことやりたい、会社の勉強会のネタにでもなればいいな、くらいの気持ちで始めたんですが、思ったより楽しく実用的なものだったので、仕事で使えるまでに至りました。
Elmを使っていて困るのは、最初の学習コストと、新しいバージョンへの対応が大変なことくらいだと思います。
ウェブアプリの実装に、関数だけのシンプルな言語を使ってみたい方にはおすすめです。ぜひ。