📝The reason for using reason #ML_study
ML勉強会 #2で話した。
要約
近年のJavaScriptは進化が激しく、様々な拡張が提案されている。その中にはMLの機能を類似したものも多数ある。例えば、オブジェクトとレコード型のように使えるようにする拡張やパターンマッチを導入する拡張といったものが提案されている。
しかし既存の言語にMLを意識した機能を追加するのは簡単ではない。
そこでFacebookのReasonそこでOCamlをJavaScriptに近づける、というアプローチを採用してる。具体的には文法をJavaScriptに近づけたり、周辺ツールの整備を行なっている。
JavaScriptの必要性
Misoca
ボクはMisocaという請求書を管理するWebサービスを作る仕事をしている。今日の交通費も出してもらった。ありがとう!
React/Redux
Webサービスを作るには、JavaScriptを扱う必要がある。
JavaScirptフレームワークは数多く存在しているが、そのうち有力なものの一つにReactとReduxがある。 Misocaでも採用している。
React
Reactはview、要はHTMLを構築するライブラリである。 今回の話とはあまり関係がないので、詳細は省略する。
Redux
ReduxはJavaScriptプリケーションの状態を管理するためのライブラリである。 以下のような型を持つ関数によって状態を遷移を管理する。
state -> action -> state
state
はその名のとおりアプリケーションの状態であり、action
はなんらかの操作を表す。 そしてこの関数はreducerと呼ばれます。 名前が一般的すぎますね。
Reduxの話はちょっとしたいので、reducerの例をいくつか見ていきます。
例: counter reducer
数を数えるreducerは上記のように定義できる。 アクションの種類(type)が INCREMENTAL
なら1を足す、 DECREMENT
なら1を引く、そうでない場合はそのままの値を返すようになっている。
これでカウンターの値という状態を管理する。
例: todo reducer
もうちょっとアプリケーションっぽいreducerも紹介する。 TODOの完了状態を切り替えるreducerは上記のように定義できる。
各項目は、固有のIDと項目の名前、完了したかどうかなどと項目として持っている。 アクションは種類をあらわすtypeフィールドに加えて、どの項目の完了状態を切り替えるかどうかを示すidフィールドを持つ。
そしてこのreducerはアクションが TOGGLE
のときは、idを確認する。 そして、idが一致したら完了状態のだけを反転させた新しい状態を作って返す。
reducer idiom
このtodo reducerはreducerでよく使われるイディオムが2つ登場している。
1つ目は TOGGLE
で使った「typeがxのとき、yという項目を持つ」である。これは、OCamlのヴァリアント型と似ている。
次は完了状態の切り替えで使っていた「状態をコピーし、一部だけ更新する」である。これはOCamlのレコードの更新と似ている。
ML由来の機能
このように最近のJavaScriptにはML系から様々な機能が輸入されている/されようとしている。
他に導入されているもしくは導入が提案されている機能としては
などがある。
more ML features
これらの機能を実現するために各種ツールを利用する。 例えば拡張されたJavaScriptからブラウザで動くJavaScriptを生成するためのBabelだとか、JavaScriptに静的な型検査を導入するflowなどを使う。
OCaml
あーあ、こんなときにバリアント/レコード型があって、パターンマッチがあって、静的型検査があってJavaScriptが生成できる言語があればなー。あー。
あっOCamlでいいじゃん。あってよかった。じゃあ使いましょう。 便利。 めでたし、めでたし。
JavaScriptとOCamlのギャップ
これで終われる世界はだいぶ幸せだが、そうもいかない。
JavaScriptとOCamlの間には大きなギャップが存在している。 それは文法だったり、ツールの使い方だったりする。
これReasonの紹介スライドにあった画像だが、途方にくれてる感があって最高だと思う。
OCamlのJavaScript化
しかしJavaScriptにMLの機能を導入するよりも、文法やツールを追加してOCamlをJavaScriptに近づけるほうが楽そうである。 少なくともReasonの開発チームはそう考えている。
Reasonがやっていること
ではReasonがJavaScriptプログラマに使いやすくするためにやっていることについて話していく。
BuckleScriptとの連携
OCamlをJavaScriptを出力できるように、BuckleScriptというOCamlからJavaScriptを生成するコンパイラと連携している。
上記の通り、BuckleScriptは人間にもかなり読みやすいコードを生成する。
人間に読みやすいコード
「人間にもかなり読みやすいコード」というのをBuckleScriptはかなり重視している。 これはREADMEにのってる例ですが、自動生成したとは思えないコードになっている。
中間言語であるlambdaからJavaScriptを生成しているこれができる、バイトコードから変換しているjs_of_ocamlとの重要な違いだ、とマニュアルに書いてあった。
余談: OCamlの魅力
BuckleScriptの資料が「なぜJavaScriptを使うか」という疑問に対しては、ブラウザで動く言語で…とかいろんな場所で動いて…とかいろいろと説明している一方、「なぜOCamlなのか」という疑問に対しては「もうしってるでしょ」ですませていた。格好いいと思う。
文法: 変数束縛
次は文法についてです。 JavaScriptの雰囲気にあうように文法が変更されている。
例えば変数束縛はletのあとのinをつけなっているし、行末には ; をつけるようになっている。
文法: 条件分岐
条件分岐も中括弧で範囲を示すようになっている。
文法: パターンマッチ
パターンマッチはmatchからswitchにキーワードが変更になっている。中括弧を明示してるのでネストしてもややこしくならない!ってマニュアルに書いてあった。
パッケージマネージャ
パッケージマネージャーはJavaScriptのnpmをそのまま使っている。 Reason自体も各種ライブラリのバインディングもnpm経由でインストールできる。普段と同じパッケージマネージャが使えるので、JavaScript使いには親切になっている。
が、npm install bs-platformするとOCamlのダウンロードとビルドがはじまるのはアツいと思う。
コード補完: Merlin
いくつかの周辺ツールも用意されている。 OCamlの補完ツールであるmerlinはそのまま使える。
エディタ拡張
各種エディタの拡張も用意されている。 Reasonはコードフォーマッタもあるので、それもエディタから使えるようになっている。
BeterErrors
既存のOCaml toolchainのラッパーもある。
例えばBetterErrorsはコンパイラのエラーを整形し、 式のどの部分で型を間違えているかをわかりやすく表示する。 エラーメッセージの整形をしているだけなので、既存のコンパイラとパイプとつないで使うことができる。
RED
またocamldebugの使い勝手を改善するREDというツールもある。
余談: Reasonという名前
気付いてると思うが、Reasonという名前は検索しづらい。というかFacebookのだしてるライブラリはflowだとかinferだとか検索しにくい名前ばかりである。
Reasonという名前のせいで、exampleの名前がおもしろくなっている。 ライフゲームが実装されたイグザンプルのプロジェクト名がreason-of-lifeなのは最高だと思う。
Reasonの利用例
Reasonがどのあたりで使われているかの話をします。
Facebook Messanger
具体的にどの部分かは分かりませんがFacebookメッセンジャーの25%はReasonに書き換えられているらしい。このTweetには書いてないですが、ブラウザ版の話らしい。
React
Reactのプロトタイピングにも使われている。 JSXをサポートしてたりと、やたらReactのサポートが厚いのが気になっていたんですが、このためかもしれない。
Trello.md
ボクもReasonつかって、Chrome拡張を書いた。Chrome拡張のバンディングは存在してなかったので、必要な部分だけ自分で書いた。
Reasonのよい面
パターンマッチ最高
予想通りですがパターンマッチ+レコード更新の組み合わせは最高だった。 「各アクションのパラメータが型で保証される」「返り値の構造が変わっていないことが型で保証される」「すべての分岐を網羅していることをコンパイラが保証してくれる」というあたりがよい。
ビルド速度
BuckleScriptはOCamlのLambdaIRを変換した上で、末尾呼び出しの除去や定数畳み込み、インライン化などの最適化をなった上で、JavaScriptを生成する。 一見時間がかかりそうに見えるが、JavaScriptにしたあとの処理のほうが遅いので、あまり気にならなかった。
無
JavaScriptにはnullやundefinedみたいな「無」みたいな値がある。 これをfunctional扱えるようなモジュールが用意されている。
sがnullでない場合のみ内容を出力する関数、sがundefinedの場合のみ内容を出力する関数は上記のようになる。
callback hell
JavaScriptでは通信はコールバックで書く必要がある。それはいわゆるcallback hellを招く。このコードはPHPですが。
OCamlには非同期モナド(lwt)があるので、これを利用して >>=
によるチェーンで書ける。 これは DelayedInc
が発行されるのをまち、その後1000ms秒待ち、 Inc
を送信しています。
Reasonのつらい面
次はつらい側面について話す。
インストール時間の増加
ReasonはOCamlコンパイラをforkして作っている。 そのため、インストール時にOCamlのビルドが必要になる。
そのため別マシンに移ったときなどにOCamlのビルドが毎回走ってしまう。CI上でOCamlのビルドが毎回はしるのでつらい。
大量の型定義
JavaScriptライブラリを使う箇所では、大量の型定義が必要になる。こんな感じの定義が延々と続くことになる。つらい。
JSON
JSONから情報を取得するのも大変で、一段ネストをすすめるためにパターンマッチが必要になる。
バリアントのランタイム表現
先ほど話した通りパターンマッチとバリアントの組み合わせは便利。
しかし、ランタイムではバリアントの名前が失なわれるため、デバッグがつらい。多相バリアントにするともっと難しくなる。
よくわかってない部分
Reasonに関してよくわかってない箇所の話をしていく。
vimハイライト
vimのプラグインも準備されいるが、 キーワードのハイライトがおかしい。 たとえば when
はキーワードだがハイライトされない。一方で普通の識別子である box
はハイライトされる。
プラグインのソースコードを読むとforked from rustと書いてあり、ハイライトがおかしい原因は分かる。 が、なんでそのままになっているか分からない。
JSCaml
JSCamlはFacebookが出しているJavaScriptをOCamlに変換するコンパイラである。 念のためにもう一度いいますが、JavaScriptをOCamlに変換するコンパイラである。
READMEに「ReasonでJavaScriptライブラリを使いたいときに、いったんJavaScriptにすると便利」とか「貧弱なデバイスでJavaScriptを実行したいときに便利」と書いてある。なんなんだ。
esy
ReasonはJavaScriptのパッケージマネージャーnpmをそのまま利用している。 しかし、Reason/OCamlのようなビルドが必要な言語との相性はあまりよくない。
そこで、yarnをforkし、ソースコードの取得とビルドを明確に区別できるようにしたesyが開発されている。
opamブリッジ
またopamとのブリッジも用意されているので、npmのライブラリもopamのライブラリもインストールできる。そのため、npmのレポジトリに大量のopamのライブラリが登録されている。
迫力がある。
参考文献
この資料を作成するにあたって参考にしたものをあげておきます。
- awesome-reason。reasonの各種資料へのリンク集。ツールや発表資料へのリンクがある。
- Dawn of Reason。 途中で何度か図を引用してます。OCamlをJavaScriptに近づければいいんだ!!という話が書いてあった。
- Reason guide。reasonの文法などはここに書いてあったり書いてなかったりする。書いてないやつはOCamlからの連想でなんとかなる。
- Bucklescript manual。 BuckleScript部分、つまりJavaScriptとの連携部分のマニュアル。が、文法はOCamlなのでReasonで使えたり使えなかったりする。
まとめ
- JavaScriptにはMLの機能が必要で、どんどん導入されている。
- でも大変なので、OCamlを使ったほうがいい。
- そのままだと大変なので周辺環境を整備したのがReason
以上です。