みずぴー日記

人間の再起動ボタンはハワイのビーチにある

📝The reason for using reason #ML_study

ML勉強会 #2で話した。

要約

f:id:mzp:20170722144752j:plain

近年のJavaScriptは進化が激しく、様々な拡張が提案されている。その中にはMLの機能を類似したものも多数ある。例えば、オブジェクトとレコード型のように使えるようにする拡張パターンマッチを導入する拡張といったものが提案されている。

しかし既存の言語にMLを意識した機能を追加するのは簡単ではない。

そこでFacebookのReasonそこでOCamlJavaScriptに近づける、というアプローチを採用してる。具体的には文法をJavaScriptに近づけたり、周辺ツールの整備を行なっている。

JavaScriptの必要性

Misoca

f:id:mzp:20170722144756j:plain

ボクはMisocaという請求書を管理するWebサービスを作る仕事をしている。今日の交通費も出してもらった。ありがとう!

React/Redux

f:id:mzp:20170722144805j:plain

Webサービスを作るには、JavaScriptを扱う必要がある。

JavaScirptフレームワークは数多く存在しているが、そのうち有力なものの一つにReactとReduxがある。 Misocaでも採用している。

React

f:id:mzp:20170722144808j:plain

Reactはview、要はHTMLを構築するライブラリである。 今回の話とはあまり関係がないので、詳細は省略する。

Redux

f:id:mzp:20170722144813j:plain

ReduxはJavaScriptプリケーションの状態を管理するためのライブラリである。 以下のような型を持つ関数によって状態を遷移を管理する。

state -> action -> state

state はその名のとおりアプリケーションの状態であり、action はなんらかの操作を表す。 そしてこの関数はreducerと呼ばれます。 名前が一般的すぎますね。

Reduxの話はちょっとしたいので、reducerの例をいくつか見ていきます。

例: counter reducer

f:id:mzp:20170722144817j:plain

数を数えるreducerは上記のように定義できる。 アクションの種類(type)が INCREMENTAL なら1を足す、 DECREMENT なら1を引く、そうでない場合はそのままの値を返すようになっている。

これでカウンターの値という状態を管理する。

例: todo reducer

f:id:mzp:20170722144824j:plain

もうちょっとアプリケーションっぽいreducerも紹介する。 TODOの完了状態を切り替えるreducerは上記のように定義できる。

各項目は、固有のIDと項目の名前、完了したかどうかなどと項目として持っている。 アクションは種類をあらわすtypeフィールドに加えて、どの項目の完了状態を切り替えるかどうかを示すidフィールドを持つ。

そしてこのreducerはアクションが TOGGLE のときは、idを確認する。 そして、idが一致したら完了状態のだけを反転させた新しい状態を作って返す。

reducer idiom

f:id:mzp:20170722144828j:plain

このtodo reducerはreducerでよく使われるイディオムが2つ登場している。

1つ目は TOGGLE で使った「typeがxのとき、yという項目を持つ」である。これは、OCamlのヴァリアント型と似ている。

次は完了状態の切り替えで使っていた「状態をコピーし、一部だけ更新する」である。これはOCamlのレコードの更新と似ている。

ML由来の機能

f:id:mzp:20170722144831j:plain

このように最近のJavaScriptにはML系から様々な機能が輸入されている/されようとしている。

他に導入されているもしくは導入が提案されている機能としては

  • パターンマッチ
  • Maybeモナドのようなnullに対する演算
  • 非同期モナドのようなコールバックの連鎖を回避する文法
  • 静的な型検査

などがある。

more ML features

f:id:mzp:20170722144835j:plain

これらの機能を実現するために各種ツールを利用する。 例えば拡張されたJavaScriptからブラウザで動くJavaScriptを生成するためのBabelだとか、JavaScriptに静的な型検査を導入するflowなどを使う。

OCaml

f:id:mzp:20170722144839j:plain

あーあ、こんなときにバリアント/レコード型があって、パターンマッチがあって、静的型検査があってJavaScriptが生成できる言語があればなー。あー。

あっOCamlでいいじゃん。あってよかった。じゃあ使いましょう。 便利。 めでたし、めでたし。

JavaScriptOCamlのギャップ

f:id:mzp:20170722144843j:plain

これで終われる世界はだいぶ幸せだが、そうもいかない。

JavaScriptOCamlの間には大きなギャップが存在している。 それは文法だったり、ツールの使い方だったりする。

これReasonの紹介スライドにあった画像だが、途方にくれてる感があって最高だと思う。

OCamlJavaScript

f:id:mzp:20170722144847j:plain

しかしJavaScriptにMLの機能を導入するよりも、文法やツールを追加してOCamlJavaScriptに近づけるほうが楽そうである。 少なくともReasonの開発チームはそう考えている。

Reasonがやっていること

ではReasonがJavaScriptプログラマに使いやすくするためにやっていることについて話していく。

BuckleScriptとの連携

f:id:mzp:20170722144859j:plain

OCamlJavaScriptを出力できるように、BuckleScriptというOCamlからJavaScriptを生成するコンパイラと連携している。

上記の通り、BuckleScriptは人間にもかなり読みやすいコードを生成する。

人間に読みやすいコード

f:id:mzp:20170722144904j:plain

「人間にもかなり読みやすいコード」というのをBuckleScriptはかなり重視している。 これはREADMEにのってる例ですが、自動生成したとは思えないコードになっている。

中間言語であるlambdaからJavaScriptを生成しているこれができる、バイトコードから変換しているjs_of_ocamlとの重要な違いだ、とマニュアルに書いてあった。

余談: OCamlの魅力

f:id:mzp:20170722144908j:plain

BuckleScriptの資料が「なぜJavaScriptを使うか」という疑問に対しては、ブラウザで動く言語で…とかいろんな場所で動いて…とかいろいろと説明している一方、「なぜOCamlなのか」という疑問に対しては「もうしってるでしょ」ですませていた。格好いいと思う。

文法: 変数束縛

f:id:mzp:20170722144912j:plain

次は文法についてです。 JavaScriptの雰囲気にあうように文法が変更されている。

例えば変数束縛はletのあとのinをつけなっているし、行末には ; をつけるようになっている。

文法: 条件分岐

f:id:mzp:20170722144916j:plain

条件分岐も中括弧で範囲を示すようになっている。

文法: パターンマッチ

f:id:mzp:20170722144919j:plain

パターンマッチはmatchからswitchにキーワードが変更になっている。中括弧を明示してるのでネストしてもややこしくならない!ってマニュアルに書いてあった。

パッケージマネージャ

f:id:mzp:20170722144925j:plain

パッケージマネージャーはJavaScriptのnpmをそのまま使っている。 Reason自体も各種ライブラリのバインディングもnpm経由でインストールできる。普段と同じパッケージマネージャが使えるので、JavaScript使いには親切になっている。

が、npm install bs-platformするとOCamlのダウンロードとビルドがはじまるのはアツいと思う。

コード補完: Merlin

f:id:mzp:20170722144929j:plain

いくつかの周辺ツールも用意されている。 OCamlの補完ツールであるmerlinはそのまま使える。

エディタ拡張

f:id:mzp:20170722144932j:plain

各種エディタの拡張も用意されている。 Reasonはコードフォーマッタもあるので、それもエディタから使えるようになっている。

BeterErrors

f:id:mzp:20170722144937j:plain

既存のOCaml toolchainのラッパーもある。

例えばBetterErrorsコンパイラのエラーを整形し、 式のどの部分で型を間違えているかをわかりやすく表示する。 エラーメッセージの整形をしているだけなので、既存のコンパイラとパイプとつないで使うことができる。

RED

f:id:mzp:20170722144942j:plain

またocamldebugの使い勝手を改善するREDというツールもある。

余談: Reasonという名前

f:id:mzp:20170722144946j:plain

気付いてると思うが、Reasonという名前は検索しづらい。というかFacebookのだしてるライブラリはflowだとかinferだとか検索しにくい名前ばかりである。

Reasonという名前のせいで、exampleの名前がおもしろくなっている。 ライフゲームが実装されたイグザンプルのプロジェクト名がreason-of-lifeなのは最高だと思う。

Reasonの利用例

Reasonがどのあたりで使われているかの話をします。

Facebook Messanger

f:id:mzp:20170722144954j:plain

具体的にどの部分かは分かりませんがFacebookメッセンジャーの25%はReasonに書き換えられているらしい。このTweetには書いてないですが、ブラウザ版の話らしい。

React

f:id:mzp:20170722144957j:plain

Reactのプロトタイピングにも使われている。 JSXをサポートしてたりと、やたらReactのサポートが厚いのが気になっていたんですが、このためかもしれない。

Trello.md

f:id:mzp:20170722145001j:plain

ボクもReasonつかって、Chrome拡張を書いたChrome拡張のバンディングは存在してなかったので、必要な部分だけ自分で書いた。

Reasonのよい面

f:id:mzp:20170722145005j:plain

パターンマッチ最高

f:id:mzp:20170722145009j:plain

予想通りですがパターンマッチ+レコード更新の組み合わせは最高だった。 「各アクションのパラメータが型で保証される」「返り値の構造が変わっていないことが型で保証される」「すべての分岐を網羅していることをコンパイラが保証してくれる」というあたりがよい。

ビルド速度

f:id:mzp:20170722145013j:plain

BuckleScriptはOCamlのLambdaIRを変換した上で、末尾呼び出しの除去や定数畳み込み、インライン化などの最適化をなった上で、JavaScriptを生成する。 一見時間がかかりそうに見えるが、JavaScriptにしたあとの処理のほうが遅いので、あまり気にならなかった。

f:id:mzp:20170722145017j:plain

JavaScriptにはnullやundefinedみたいな「無」みたいな値がある。 これをfunctional扱えるようなモジュールが用意されている。

sがnullでない場合のみ内容を出力する関数、sがundefinedの場合のみ内容を出力する関数は上記のようになる。

callback hell

f:id:mzp:20170722145021j:plain

JavaScriptでは通信はコールバックで書く必要がある。それはいわゆるcallback hellを招く。このコードはPHPですが。

OCamlには非同期モナド(lwt)があるので、これを利用して >>= によるチェーンで書ける。 これは DelayedInc が発行されるのをまち、その後1000ms秒待ち、 Inc を送信しています。

Reasonのつらい面

次はつらい側面について話す。

インストール時間の増加

f:id:mzp:20170722145032j:plain

ReasonはOCamlコンパイラをforkして作っている。 そのため、インストール時にOCamlのビルドが必要になる。

そのため別マシンに移ったときなどにOCamlのビルドが毎回走ってしまう。CI上でOCamlのビルドが毎回はしるのでつらい。

大量の型定義

f:id:mzp:20170722145036j:plain

JavaScriptライブラリを使う箇所では、大量の型定義が必要になる。こんな感じの定義が延々と続くことになる。つらい。

JSON

f:id:mzp:20170722145040j:plain

JSONから情報を取得するのも大変で、一段ネストをすすめるためにパターンマッチが必要になる。

バリアントのランタイム表現

f:id:mzp:20170722145043j:plain

先ほど話した通りパターンマッチとバリアントの組み合わせは便利。

しかし、ランタイムではバリアントの名前が失なわれるため、デバッグがつらい。多相バリアントにするともっと難しくなる。

よくわかってない部分

Reasonに関してよくわかってない箇所の話をしていく。

vimハイライト

f:id:mzp:20170722145050j:plain

vimプラグインも準備されいるが、 キーワードのハイライトがおかしい。 たとえば when はキーワードだがハイライトされない。一方で普通の識別子である box はハイライトされる。

プラグインソースコードを読むとforked from rustと書いてあり、ハイライトがおかしい原因は分かる。 が、なんでそのままになっているか分からない。

JSCaml

f:id:mzp:20170722145055j:plain

JSCamlはFacebookが出しているJavaScriptOCamlに変換するコンパイラである。 念のためにもう一度いいますが、JavaScriptOCamlに変換するコンパイラである。

READMEに「ReasonでJavaScriptライブラリを使いたいときに、いったんJavaScriptにすると便利」とか「貧弱なデバイスJavaScriptを実行したいときに便利」と書いてある。なんなんだ。

esy

f:id:mzp:20170722145059j:plain

ReasonはJavaScriptのパッケージマネージャーnpmをそのまま利用している。 しかし、Reason/OCamlのようなビルドが必要な言語との相性はあまりよくない。

そこで、yarnをforkし、ソースコードの取得とビルドを明確に区別できるようにしたesyが開発されている。

opamブリッジ

f:id:mzp:20170722145103j:plain

またopamとのブリッジも用意されているので、npmのライブラリもopamのライブラリもインストールできる。そのため、npmのレポジトリに大量のopamのライブラリが登録されている。

迫力がある。

参考文献

f:id:mzp:20170722145107j:plain

この資料を作成するにあたって参考にしたものをあげておきます。

  • awesome-reason。reasonの各種資料へのリンク集。ツールや発表資料へのリンクがある。
  • Dawn of Reason。 途中で何度か図を引用してます。OCamlJavaScriptに近づければいいんだ!!という話が書いてあった。
  • Reason guide。reasonの文法などはここに書いてあったり書いてなかったりする。書いてないやつはOCamlからの連想でなんとかなる。
  • Bucklescript manual。 BuckleScript部分、つまりJavaScriptとの連携部分のマニュアル。が、文法はOCamlなのでReasonで使えたり使えなかったりする。

まとめ

f:id:mzp:20170722145112j:plain

  • JavaScriptにはMLの機能が必要で、どんどん導入されている。
  • でも大変なので、OCamlを使ったほうがいい。
  • そのままだと大変なので周辺環境を整備したのがReason

以上です。