elmのコンパイラがめっちゃ遅かったから速くしたけどまだ遅かったからもっと早くした

Fringe81 Advent Calendar 2018の21日目です。

[elmのコンパイラがめっちゃ遅かったから速くした](https://qiita.com/4245Ryomt/items/20712197c9b4b76f0f5b)の続編

# 忙しい人のためのネタバレ
Elmのコンパイラバージョンを上げてコンパイルが１００倍早くなった


# 前回までのあらすじ
Elmで実装されたフロントエンドアプリケーションのコンパイル時間が半端なく長くなってしまった！
そこでヒープサイズを盛ることによって3倍くらい改善して、250秒くらいから30秒くらいにはなった！

# 前回のエピローグより...
*0.19ではRTSオプション気にしなくても超早くなりました。*

# Road To 0.19
超早くなるといってもプロダクトのコードやってなかったのでやっていくぜ！

## やる前のインプット
- https://github.com/elm/compiler/blob/master/upgrade-docs/0.19.md を読む
- https://github.com/avh4/elm-upgrade#elm-upgrade-- を読む

## 読み物によってインプットしたことを実施していく
- まずは`elm-upgrade`をポチッとな。
- あとは`elm-upgrade`が出してくれたあとは`コレやってねリスト`にしたがっていく...

## 「ここからが本当の地獄だ...]
- `コレやってねリスト` -> 注文が多い
 - 自分らで作ってたイケてる演算子を抹消 & 関数呼び出しに置き換える
 - `Date`モジュールに依存していたところを`Time`モジュールを使っていい感じにするようによしなにする
 - CustomTypeに対してtoStringを使っていたところを専用のtoStringを定義してあげる
 - `Html.style`を使っていたところをどうにかする
 - `elm/regex`の変更に対応するように変更する
 - Debug.crashを抹消する
 - etc...
 
*やること多いな！*

モジュールが新しくなったり、関数の返す型が変わったりプロダクトのソースコードもかなり変更が必要だなというのが`elm-upgrade`によってわかったところですべての対応を一気にやろうとするのは骨が折れそうなのでチェックポイントを設けて実施することにしました。

フロントエンドアプリの構成が当時は２つのElmアプリケーションからできていて、片方は１画面でもう片方が複数ページを扱うような感じでした。

なので１画面アプリケーションのほうから着手してみて問題がないこと、やりきれること、がわかったらもう片方も着手することにしました。

さらに、Debug.crash等のDebugモジュールは抹消しなくてもビルドやアプリケーションを動作させることは叶うので作業を分けてみることにしました。`とくにDebug.crashはMaybe型を強制的にunwrapするために使ってたという事実があり抹消が困難だろうという目算がありました。 `

そこで立てた計画が以下になります

```
1画面アプリケーションの0.19移行
- `Html.style`等を0.18のころのように扱えるファサードの実装
- 各種ライブラリのバージョンアップ追従のための変更
- Debugモジュールの抹消

*↓は↑が行けたら行く　*

複数アプリケーションの0.19移行
- 各種ライブラリのバージョンアップ追従のための変更
- Debugモジュールの抹消

```

↑を実施する上での戦いを紹介しておきます↓

### `Html.style`を使っていたところをどうにかする
- *もうElmからあんまstyleを定義すんなよ！* とHtml.Attributes.style関数のドキュメントにあるようにもうあんまやっちゃだめなんだな！とわかりつつ、今までElm側でstyleを指定していたものをcssに分離するのは大変だなあという甘えがあったので以下のような関数を定義して`逃しました。`

```
styles : List ( String, String ) -> List (Html.Attribute msg)
styles =
    List.map (\( propname, propvalue ) -> Attr.style propname propvalue)
```

使う側としては

```elm
-- elm 0.18 ver
viewStyle = style [("width", "100px")]

view = div [ viewStyle ] []
```

```elm
-- elm 0.19 ver
viewStyle = styles [("width", "100px")]

view = div ([] ++ viewStyle) []
```

美しくないな！やCSSかけや。と感じつつ一旦コンパイルを通すという目標のために`逃げ`ました。
真似せずCSSを書いてクラス名でしたほうがいいですね！

### Dateモジュールに依存していたところをTimeモジュールを使っていい感じにするようによしなにする

DateモジュールがリストラになってTimeモジュールを使ってネということになったので
Date型でフィールドを定義していたレコードはすべてTime.Posix型に変更しました。

yyyy-mm-ddとして表示するようなタイムゾーンに依存していたところは`asia/tokyo`のZone型の値を用意しておくような対応をしました。

```elm
-- 0.18時点
formatYYYYMMDD : Date -> String
```

```elm
-- 厳密にやると以下のようにしたいところですが。(Time.yearやTime.monthがZone型を取るため)
formatYYYYMMDD : Zone -> Time.Posix -> String

-- 関数の中でZone型の値はすべて`asia/tokyo`のものを勝手に渡すものとして以下のように定義していきました。
formatYYYYMMDD : Time.Posix -> String
```

プロダクト的に表示される時間はすべて日本時間でも問題なかったので↑のような`逃げ`方ができました。

### `elm/regex`の変更に対応するように変更する

`elm/parser`を使ってネ！と丁寧に`elm/regex`のREADMEで書かれていた気がするが、オラア正規表現が使いたいんや。プロダクトのコードを変更させんでくれ。というお気持ちで`elm/parser`は華麗にスルーしたところで
`elm/regex`の変更へ追従していく変更を開始しました。

Regex.regexという引数に文字列をとるRegex型のファクトリ関数があったので今まで以下のようにしていました。

```elm

someRegex = Regex.regex "なんか正規表現文字列"
```

0.19からはRegex.fromStringという関数に変わりました。

```elm

someRegex = Regex.fromRegex "なんか正規表現文字列"
```

しかしこの関数Maybeを返すようになりました。
関数に渡した文字列が正規表現として間違っていたらNothingで返してくれるというなんとも便利な仕様です。

が、0.18のころからコードをあまり変えたくないというワガママな私には困ったものでした。
ということで↓のように関数を定義してここでも`逃げ`ました。

```elm
fromRegexUnsafe regexString = 
    Regex.fromRegex regexString |> Maybe.withDefault Regex.never
```

ここまで作業をすると`elm make`は実行できるようになります。
そしてここからは`--optimize`フラグを利用するためにDebugモジュールを使わないようにしていきます。

### Debug.crashを抹消する

お恥ずかしいところですが、
Maybe型やResult型の値を強制的にとりだすためにDebug.crashを使っていました。

```elm
-- 真似しちゃだめよ！！！！！
sure : Maybe a -> a
sure maybeValue = case maybeValue of
    Just value -> value
    Nothing -> Debug.crash "ありえないんだぜ"
```

ここは地道にこういうことをしている関数がすべてMaybeを返す or Resultを返すように定義していきました。
とくにサーバサイドとのコミュニケーションをするようなコードではコレが蔓延していたのでとても辛い戦いになりました。


## すべての作業が終わった結果
```

before(0.18)
npx elm-make src/Main.elm --output app.js  384.90s user 136.40s system 355% cpu 2:26.53 total

before(0.18 & https://qiita.com/4245Ryomt/items/20712197c9b4b76f0f5b)
npx elm-make src/Main.elm --output app.js 59.28s user 13.68s system 128% cpu 56.635 total

after(0.19)
npx elm make src/Main.elm --output app.js  1.88s user 0.34s system 65% cpu 3.380 total
```

圧倒的改善...!

300倍以上早くなってますね！

これで生産性も300倍！！！


#　やり終わって

- 0.18と0.19が混在していた時期に手の混んだwebpackのコンフィグ作ったりココに書き切れないくらい苦労はあった。
- バージョンアップで苦労したところってこれはイイのかな。。。ホントは良くないんだろうな。。。と思っていたところだった。
 - Debug.crashをデバッグ以外の用途で使う
 - Date型を直接持つ(タイムゾーンを意識しないで時間をどうにかするアプリをつくることも)

結構学びはあった。

# ご注意
複数ページアプリは０.19にして問題もでた。`--debug`フラグをつけてコンパイルできなくなっちゃう〜というコンパイラのバグに遭遇してelmの便利開発ツールが有効にできてない。
300倍のコンパイル速度と天秤にかけて、そのうち治してくれるだろうということで今は我慢している。
https://github.com/elm/compiler/issues/1817#issuecomment-440194948