Runner in the High

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

dayjsと謎の挙動

年と月だけの文字列をDateへ変換すると2月だけおかしくなる

console.log(dayjs('201901', 'YYYYMM').toDate()); // Tue Jan 29 2019 00:00:00 GMT+0900 (日本標準時)
console.log(dayjs('201902', 'YYYYMM').toDate()); // Fri Mar 01 2019 00:00:00 GMT+0900 (日本標準時)
console.log(dayjs('201903', 'YYYYMM').toDate()); // Fri Mar 29 2019 00:00:00 GMT+0900 (日本標準時)
console.log(dayjs('201904', 'YYYYMM').toDate()); // Mon Apr 29 2019 00:00:00 GMT+0900 (日本標準時)

なんで?

github.com

新しいフレームワークを学ぶならTodoMVCではなくRealWorldを参考にしよう

よく新しいフレームワークを学ぶにはTodoアプリを作ってみるのがよい、と言われる。実際、Todoアプリを様々なフレームワークで作ってみたサンプルをまとめたサイトもあったりする。

ところが、実際に業務で作るようなアプリケーションはTodoアプリの範疇を超えている。とくにSPAにもなると、画面遷移やWebAPI連携、大規模な状態管理などなどの条件が増えるので、Todoアプリを作っているときには考慮できていなかった大変さが出てくる。

そこで参考になるのが RealWorld example apps と呼ばれるプロジェクト

f:id:IzumiSy:20190609165127p:plain

端的に言うと、TodoMVCの大規模版

規定のスペックに沿って、様々なウェブフレームワークで作られたアプリケーションのリポジトリがリストアップされている。

f:id:IzumiSy:20190609165323p:plain

スペックについて

"Conduit" is a social blogging site (i.e. a Medium.com clone). It uses a custom API for all requests, including authentication.

要求されているスペックはConduitと呼ばれるMediumクローンを作る、というもので、具体的には以下のような機能を満たしているとのこと。

  • ホーム画面
  • JWTを使ったユーザー登録・ログイン画面
  • ユーザーの設定画面
  • 記事の投稿/編集画面
  • 他ユーザーをフォローする
  • お気に入り記事一覧

ここまでの機能を実装しているアプリケーションとなると、わりとかなり本格派なんじゃないだろうか。

ちなみに、フロントエンド以外にもサーバーサイド・フレームワークによる実装もあるので、サーバーメン的にも参考になると思われる。

"初夏のJavaScript祭り2019" にElmのはなしで登壇した

 6/1に開催されたJavaScript祭りというイベントに「jQueryからElmまで」というタイトルで15分枠の登壇をした

 内容はjQueryからJavaScriptを触り始めた自分が、Elmを書くまでにどういう困難を経験してどういう学びを得たのか、にフォーカスを当てたもの。

 フロントエンド界隈はトレンドの変化が早いと言われがちだが、闇雲に新しいフレームワークが乱立しているというわけではなく、どのフレームワークもアプリケーションを作る上での問題を解決するために生まれているのは間違いない。これはjQueryprototype.jsのころから変わっていないし、そもそもテクノロジー自体が問題解決をするためのものであることからも自明だと思う。そう考えると、いまElmを書いている自分は割と遠いところまで歩いてきたなぁ、と感じさえする。

Prop Drilling について

 懇親会の際に、スライドの中で触れていた Prop Drillingアンチパターンなのか? という話になったが、これは正直スライドの表現が悪かったと思っている。

 自分としては、Prop Drilling はその後に触れられるイベント・エミッタを乱用するパターンを説明するきっかけとして使いたいだけで、アンチパターンだとは思っていない。まずモジュール結合度の観点からみても、コンポーネントに渡されるパラメータが増えても結合度は低い状態であるし、コンポーネントに対する入力が増える以外の欠点がないからだ。

余談

 もともと、この発表のインスピレーションは過去のこの記事にある izumisy-tech.hatenablog.com

 ReactのSFCやChoo.jsなどを知ってときに「ビューって関数やん!」と気付いたのを覚えている。

 その後、偶然にもElmを使っている会社へ新卒入社し、前提としてフロントエンド・アプリケーションは関数の組み合せで作れるということが自分の中での当たり前になっていった。一方で、その考えに至るまでに自分が書いていたフロントエンドは全く異なるものだったことを思い出した。一度なにかを知ってしまうと、それを知らなかった頃の自分には戻れない。それでも、どのように自分の考え方が変わっていったかはまだ思い出せる。こんなことを考えながら、今回はスライドを作った。

 登壇にあたっては、最終的にはJavaScriptにトランスパイルされるとはいえ、ElmはほとんどJavaScriptとは別の言語なので参加者層的に楽しんでもらえるかなという不安もあった。事前に主催の方から頂いたアンケートをみてもやはりVue.jsを書いている、という方が多かったし、発表内容にもVue.jsとVuexにフォーカスを当てたものが多かった。

 かくいう自分も、仮に開発対象のアプリケーションがのちのち自分の手を離れることになる場合、あるいは組織自体がElmをキャッチアップできる状態でない場合には、Elmを選択することはいまのところないだろうなと思っている。Vue.jsやVuexであれば、コミュニティの大きさから後任の開発者が確保できる可能性が充分にあるからだ。

 Elmの堅牢な型システムやある意味 "ズル" できないアーキテクチャは、必ず大規模なアプリケーションに必要なものであることには間違いないが、そもそも書ける人間がいない状態になってしまっては元も子もない。とはいえ、JavaScriptを触っている人間であれば、文法がパッと見で目新しいものあるだけで、Elmにはすぐに馴染めると信じている。そのような意味で、自分の発表を聞いてElmの存在だけでも知ってくれる人が増えてくれたとしたら非常にありがたい。

2019年現時点でのElmベストプラクティス6選

先日業務で1からElmアプリケーションを作りきったのでそのときの学びをメモっておく。

1. Model / Msg / View のような分割をしない

  • Rails などのフレームワークからきた人がやりがち。
  • Elm でファイル分割をするのはモジュール単位でのカプセル化をするときだけでよい。
  • なので基本的に1画面につき1モジュールとして、その中に Model / Msg / View / Update などを書いていく。
  • ここからさらにデータ構造として抽出できるものがあれば後述の Opaque Type として切り出す。

2. コンポーネント指向と混同しない

  • Elm は React や Vue.js のようなコンポーネント指向フレームワークではない。
  • 基本的に1画面につき1モジュールとして作る。
  • 再利用したい画面のパーツは関数として作ればよい。

3. Opaque Type の活用

  • Elmにはクラスはないが、モジュールシステムを使うことで公開する型、関数などを制限したカプセル化ができる。典型的なものは以下のふたつ。

3.1. 値オブジェクト

アプリケーションのドメインの固有な型を作ることで仕様をカプセル化する

module Bookmark.Title exposing (Title, new)

type Title =
    Title String

new : String -> Title
new value =
    Title value

ただ String をカプセル化しているだけにも見えるが、このように型を定義することで String をアプリケーション固有のドメインとして特化させることができる。

これには以下の利点がある

  • バリデーションなどのロジックをこのモジュールに実装することで責務分割がしやすくなる。
  • 関数のシグネチャから利用意図が分かりやすくなる
    • String -> String -> StringよりもTitle -> Description -> Bookmarkのほうが、コードに与えられた意味が理解しやすい。

3.2. コレクションオブジェクト

これが意外と使いドコロが多い。集合を型として表現したカプセル化

例えば以下のような Bookmark 型があるとする

-- Bookmark

module Bookmark exposing (Bookmark, isInvalid)

type Bookmark
    = Valid Title Description
    | Invalid
 
 
isValid : Bookmark -> Bool
isValid bookmark =
    case bookmark of
        Valid _ _ ->
            True
      
        Invalid ->
            False

その上で Bookmark のコレクションを扱う Bookmarks 型を定義する

-- Bookmarks
-- 上で定義したBookmarkの集合を表現した型

module Bookmarks exposing (Bookmarks, new, size)

import Bookmark exposing (Bookmark)


type Bookmarks =
    Bookmarks (List Bookmark)


new : List Bookmark -> Bookmarks
new bookmarks =
    Bookmarks bookmarks
  
  
size : Bookmarks -> Int
size bookmarks =
    bookmarks |> List.filter Bookmark.isValid |> List.length 

このコレクションオブジェクトのいいところは利用者側がコレクションの仕様を詳しく知らずに使えること

  • Bookmarks のコレクションが何で実装されているか (List, Set, etc...) は利用者側は意識しなくてよい
  • Bookmark のサイズがどのような基準で計算されるかは利用者側は意識しなくてよい

言い換えれば bookmark のコレクションにまつわるロジックの変更はすべてこのモジュールに閉じたものになる。

4. Record をインターフェースとして使う

Record はデータ構造に別名をつけただけのもの (type alias だし)

0.19からはタプルが2値しかとることができなくなったぶん Record が3つ以上の値をもつタブルの代替になった感がある。

Extensible Record を使った依存の切り離し

Record を部分的に満たすインターフェース (Extensible Record) を使うことによって特定のデータ構造への依存を分離できる

たとえば以下のコードは toSessionが Model に依存している

type alias Model = 
    { session : Session
    , currentUser : User
    , bookmarks : Bookmarks
    }
  
  
toSession : Model -> Session
toSession { session } =
    session

これはよくある例で、Model はデータが多くなればなるほど多くの関数が依存するようになりがちだ

実際には toSession 関数は session だけが存在するレコードが受け取れればいいので、以下のように Sessionable として定義した Extensible Record に依存させ Model の依存を取り除ける。

type alias Sessionable a =
    { a | session : Session }
  

toSession : Sessionable a -> Session
toSession { session } =
    session

この分離の利点は以下

  • テストする際に Model を丸ごとモックする必要がない
  • 依存の方向を統一できる (DIP)

特に Elm は循環参照をコンパイラが許さないので、アプリケーションが大規模になればなるほど Extensible Record の利用シーンは増える。

5. データ構造的な DRY よりも状態網羅性を大事にする

定義上の重複を減らそうとするのではなく、状態の網羅性の観点から適切かを考える

type Page
    = SignIn
    | Bookmarks
    | NewBookmark
    | Loading

type alias Model =
    { session : Session
    , currentUser : Maybe User
    , bookmarks : Maybe Bookmarks
    , currentPage : Page 
    }

上記の Model の欠点は以下の2点

  • Maybe の意図が不明確
    • bookmarkscurrentUser が Nothing なケースが定義から分からない
  • ありえない状態がとれる
    • currentPage が SignIn なのに Bookmarks が存在している、など

これを踏まえた上で、上記よりも以下のような Model がより望ましい

type Model
    = SignIn
    | Loading Session
    | Bookmarks User Session Bookmarks
    | NewBookmark User Session Bookmarks

この定義を見ると、SessionBookmarks を共通化したい気持ちがでてくるが、状態の網羅性が壊れるくらいであれば共通化はしないほうがいい。共通化するよりも堅牢さを大事にする。

6. Parcelを使うと最速で環境構築できる

いまのところこれが最速だと思う。

qiita.com

Immutable.jsを使うメリット

 先日、新卒で入ったエンジニアが 「Immutable.jsの研修課題をやってるんですけど、正直なんで必要なのか分かんないっす」 と言っていた。

 たしかに React, Redux と Immutable.js をセットでつかおうみたいなノリの記事はネットでよく見るが、じゃあなんでそのセットなの?という点に関してはあまり詳しく書かれていないことのが多い気がしたので、個人的にその理由っぽいのを雑に書き残しておこうと思う。

イミュータビリティのいいとこ

  • コーディング・バグを減らす
    • 言語仕様上ミュータブルな JavaScript は、大勢で開発してるとこっそりどこかで参照を持ったオブジェクトを書き換えてた、なんてことになりやすい。なのでデータを更新する際にはイミュータブルであることが保証できるとバグが起こりにくいコードを書ける
  • メモリ効率がいい
    • イミュータブルなオブジェクトは中身が同じなのでコピーが参照のみになり、実行時のメモリ効率がよくなる
    • 参照がコピーされるだけなので、例えばコレクションの比較も実質 O(1) になるので速い
  • 地味にWikipediaイミュータブルのページが充実した内容で参考になる

ReactとRedux

  • 仕様的に React+Redux 自体がイミュータビリティを期待したものになっている
  • React が Unidirectional なデータの流れ方をする
    • コンポーネントの中で明示的に setState が呼ばれて初めてビューの更新が予約される。
    • この更新はミュータブルなステートの部分更新などではなく、新しいステートを丸ごと与える。
  • Redux が関数型アプローチ
    • React 界のFlux実装のデファクトスタンダードこと Redux はステートの変更を純粋関数で行う
    • 変更を行う Reducer が React のステート更新と同様に既存のステートから新しいステートを返す(イミュータブル)
  • この辺の前提に加えて、ES6だけで更新処理周りのコードを書いていると、スプレッド演算子Object.assign まみれになるところが、Immutable.jsでスマートに書けたりする。
    • 最近のバージョンでは TypeScript と組み合わせたトランスパイル時のチェックもそこそこいい感じになってきている

他の一部フレームワークとの相性

  • Vue.js で Vuex に載せるデータを Immutable.js のコレクションにしたら微妙だった
    • そもそも Vuex 自体がステートをミュータブルで持つので、どこがミュータブルでどこがイミュータブルなのかを気をつけないと意図しない挙動が起きることがあった
    • そもそも Vue.js では Object.defineProperty と呼ばれる更新検知の機構を使って効率的にオブジェクトの更新を検知しているので、逆に Immutable.js を使わないほうがパフォーマンス的にもよくなる気がするし、むしろ使うべきじゃなかったと思っている。
  • Boost the Performance of an AngularJS Application Using Immutable Data · Minko Gechev's blog
    • ちょっと古いが Immutable.js を AngularJS と使った人の記事
    • AngularJS は Digest Loop の中で scope オブジェクト間の差分チェックをして、もし差分があればそれをビューに適用していく仕組みになっている。これがコレクションになると O(n) になるが、 Immutable.js を使うとコレクション比較が実質 O(1) なのでパフォーマンスが向上する。
    • この作者は immutable というディレクティブを自作したらしいが、 AngularJS 組み込みの filter ディレクティブなどとの相性が悪いらしく率直に言って使いづらそうだ。
  • Immutable.js は JS のプレーン・オブジェクトへ変換する処理のオーバーヘッドがそこそこデカいので、ユースケース上ここがボトルネックになってくるとどのフレームワークと組み合わせていてもイミュータビリティが破綻し始める感がある。
    • Wantedly People のフロントエンドを作ってる人がこのつらみをどこかで書いていた気がする

まとめ

  • React と Redux 自体がそもそもイミュータビリティとの相性がいいフレームワークになっているという印象。他のフレームワークだと場合によっては微妙。 React+Redux でやるなら Immutable.js は入れておいて損がないよ、と言われる理由はだいたいこんなもんかな。
  • 余談だが Redux に影響を与えたと言われる Elm は全ての関数が最初からイミュータブル。もはや言語レベルでこうなっているのは結構嬉しかったりする(チームでコンセンサスを得る必要がなかったりするから)
  • 最近では Immer という Immutable.js オルタナティブが出たらしい。パット見ではコレクション系のクラスっぽいものがなくAPIが非常にシンプルという印象。こっちも要チェックっぽい。