Runner in the High

技術のことをかくこころみ

エンジニアHubで「Elm入門と実践」を寄稿した

エンジニアHubさんというあの全日本的に有名なWebメディアでElmの記事を執筆しました。

employment.en-japan.com

これまでにTypeScriptの記事とかはあったものの、関数型AltJSの記事はたぶん今回のElmが初めてです。

なんだかんだ国内でも少しづついろんな盛り上がりがでてきているこのタイミングでエンジニアHubさんにElmの記事を書けたというのはとても神がかり的なタイミングだったな〜と思っております。 本当にすごくいい経験になったし、これでElmに興味を持つ人が増えてくれれば感無量です。

記事で使われているサンプルアプリケーションのソースコードGithubで公開しました。ぜひElmアプリケーションを開発する際には参考にして頂けるかなと。

github.com

記事の背景

今回の記事は入門というタイトルが付いているものの、実際の記事の意図としては文中にもある通り、TODOアプリ的な小さなアプリケーションから、リチャード・フェルドマンのelm-spa-exampleのような極めてリアルなプロダクトに近いElmコードの間を埋める"ラダー"のような立ち位置の記事にしたい、というものがありました。

編集の方から最初に執筆のお話を頂いたときも、やはりインターネットにおけるElmの学習リソースの偏り、つまり「書き始めるのは簡単だけれど、ある程度規模のあるアプリケーションを作るためのノウハウがまだ見つけられない」というのはすごくあるなと自分でも感じていて、それを埋められるような記事を書けたらいいだろうな、と考えながらトピックを選択しました。

もちろん、これはElmに限った話ではないんですが、それでもやはりReactやVue.jsと比べて、いかにElmアプリケーションを"大きく"育てていくかというトピックはまだまだ少ないとは僕も感じています。 そんな背景を踏まえた上で、自分の記事がElmを学ぶエンジニアたちの手助けとなるラダーになってくれれば嬉しいです。

モデルのデータをどこで計算するのか問題

余談ですが、記事を書いている際、弊社のエンジニアのレビューでサンプルアプリケーションの設計に少し悩んだこと部分がありました。 それは、画面で表示されるデータをモデルからどのタイミングで計算するのかというところです。

今回自分が寄稿したエンジニアHubのECカートアプリは、update関数がモデルに保持しているデータをもとに次のデータを計算してモデルに格納しています。 たとえば、サンプルコードにおけるCart型は以下のような定義になっており、taxesやshipping等の値はproductIdsによって従属的に決定される、という形です。

type Cart
    = Cart
        { productIds : ProductIds -- これがshippingやtaxesの値を決定する
        , shipping : Int
        , taxes : Int
        , subTotal : Int
        , total : Int
        }

モデルに計算結果が予め格納されていることのメリットは

  • update関数に計算を凝集できるためview関数をシンプルにできる
  • モデルの状態から画面が予測しやすいためデバッガビリティが高い

というものがあり、今回のサンプルコードでは個人的な推しとしてこのスタイルを採用しています。 実際に文中でもこんなことを書きました。

適切に設計されたアプリケーションのModelはViewのコードに現れます。Viewの中でModelのデータを複雑に計算しているとしたら、それはModelのデータ構造の設計を見直す必要があるでしょう。 Modelのデータに文字を付け加えたり、case文でパターンマッチによるHTMLの出し分けをしている程度になっているのが、Viewの責務としては理想です。

Elm入門と実践 - 買い物カートを作ってアーキテクチャ「TEA」を学ぶ - エンジニアHub|若手Webエンジニアのキャリアを考える!

しかし、ElmにおいてモデルというのはいわゆるSingle Source of Truthにあたるものであるため、たとえばモデルにデータAという値が格納されている場合、その値から計算される別のデータBという値が同じようにモデルに格納されているのは、実質データAだけで計算可能な値を重複して格納しているためSSoTに違反しており、結果として誤ったデータを格納する原因になるのではないかという指摘です。

たとえば、サンプルコードにおけるCart型は以下のような定義になっており、taxesやshipping等の値はproductIdsによって従属的に決定されますが、フィールドとして存在していることでコーディングミスなどで誤った値が入る可能性があるということです。 ではどうするのが良いかと言うと、これを以下のようにproductIdsだけにします。

type Cart
    = Cart ProductIds

もともと存在していたshippingやtaxesなどの値は、別途view関数などの中で計算します。Lazyなどを使っていれば実質ProductIdsが変わっていなければDOM操作は行われないため計算コストはさほど問題ありません。

正直、これには決まった答えがないのでどちらのスタイルを採用するかというのはチームで話し合ってキメをやっていくしかない、いわゆる要はバランス™としか言えないのが事実です。 個人的にはどっちを採用してもいいかなとは思っています。合意がとれればそれでOK、という感じで。

結論

Elmって最高だね!

基礎からわかる Elm

基礎からわかる Elm

  • 作者:鳥居 陽介
  • 発売日: 2019/02/27
  • メディア: 単行本(ソフトカバー)