Cmd/Subを提供するeffect moduleの中身が気になって気になって、ElmのスケジューラーとEffect Managerのコード(Schedule.js,Platform.js)を読んでみました。(以下はElm0.18での話です)
##Elmのスケジューラー
今のElmのスケジューラーは0.17の時に作り変えられました。
A Farewell to FRPに書いてあるのですが、Erlang、Elixirで使われているBEAM VMを参考にしたそうです。
(あと、ElmのTask型はスケジューラーの一部のラップ。)
- Elmのスケジューラーには、処理の単位であるタスクに当たるものがあって、タスクの塊を実際に処理する実行単位をプロセスとしています。
- プロセスにはmailboxというものがあり、他の処理からメッセージを出すことが出来ます(send)。
- mailboxからメッセージを取り出し、関数に渡して実行するreceiveタスクが定義されています。
- これらの処理はすべて非同期的に行われます。
###mainProcessとは
receiveに渡す関数に状態を持つようにして、andThenタスクを使いタスクをループさせます。
すると、メッセージを受け取ると関数が状態を更新して、またメッセージがあると関数が状態を更新して、といったElmアーキテクチャの挙動になります。
ループさせている部分
function spawnLoop(init, onMessage)
{
var andThen = _elm_lang$core$Native_Scheduler.andThen;
function loop(state)
{
var handleMsg = _elm_lang$core$Native_Scheduler.receive(function(msg) {
return onMessage(msg, state);
});
return A2(andThen, loop, handleMsg);
}
var task = A2(andThen, loop, init);
return _elm_lang$core$Native_Scheduler.rawSpawn(task);
}
function onMessage(msg, model)
{
...
}
var mainProcess = spawnLoop(initApp, onMessage);
このループさせたプロセス(mainProcess)でThe Elm Architectureは処理されています。
##Cmd/SubとEffect Manager
今回のターゲット。Cmd/SubとEffect Managerについて。
ブラウザ上のアプリケーションには無数の非同期、副作用のある処理があります。
The Elm Architectureでそれらを管理する仕組みがCmd/SubとEffect managerです。
集めて管理しなければならないブラウザ上の副作用には以下のようなものがあります。
- イベント等への関数の登録、削除
- setTimeoutや、requestAnimationFrameによる別プロセスの立ち上げ
###Cmd/SubとEffect Managerの流れ
Cmd/Subを一つにしてprogramに渡します。
Cmd.mapを使うと、親コンポーネントに合わせてMsgを大きく出来ます。
Cmd.batchを使うと、複数のCmdを連続して実行する一つのCmdに出来ます。
Cmd/Subをprogramに渡すと、Cmd/Subに対応したEffect Managerへリストになって渡ります。
そのCmd/Subのリストを見て、Effect Managerは様々な面倒くさいことをThe Elm Architectureの下でやってくれます。
例えば、親コンポーネントと子コンポーネントが、ルートのDOMのイベントに関数を登録する必要がある場合(Sub)一つの登録にしたり、使わなくなったら登録を解除したり、
setTimeoutで返って来たプロセスIDを取り回して管理したり。
(なので使わなくなったCmd/Subを適切にnoneにするのは公開するコンポーネントの場合大事となります。)
最後にCmd/Subの結果はMsgになってThe Elm Architectureに返ってくるようになっています。(Cmd Msg
)
###Effect Manager周りのプロセス全体像
で知りたかったeffect moduleのコードの意味です。
Elmが起動すると、以下のようなプロセスが用意されます。
すべては疎結合、非同期で、Elm Archetectureの純粋へ影響がないようになっています。
###effect moduleの中身
Cmd/SubとEffect Managerを提供するmoduleは、effect moduleという名前になっています。
- effect moduleにはCmdのみ、Subのみ、Cmd/Subの両方の3タイプある。
- initがEffect Manager Process本体。
- onEffectsが、main ProcessからCmd/Subがリストで送られてきた時に実行される関数。
- Platform.sendToAppでmain ProcessへMsgを送る
- Platform.sendToSelfで自分のProcessへMsgを送る。
- onSelfMsg関数はsendToSelfされると呼ばれる。
- onEffectsとonSelfMsgは状態を変化させることが出来る。
###effect moduleもユーザー側が書いてはいけない
effect moduleは書く必要はないです。
-
状態があって出入力のあるものが欲しいなら、The Elm Archetectureスタイルで公開できる。
-
ファイルapiのライブラリをみると、Task型のラップですんでいたり。
-
Portでよい。
-
既にCmd/Subで公開されているものを組み合わせて作れる。
-
集めて管理しなければならないブラウザ上の副作用レベルの問題なのかどうか
##終わりに、Elmアーキテクチャのバージョンと裏側の変化
Elmアーキテクチャには初期のバージョンと、非同期について考慮された現行のバージョンの2つがあります。(初期のバージョンもbeginerProgram関数で使える)
バージョンの過程
- Elmにまだ非同期が実装されていない頃、初期のバージョンできる。
- Elmに非同期(Task型)実装され、現行のバージョンが出来る。
- ElmがFRPをやめる。
その時の裏側は
- シンプルなオブサーバパターン
- (読んでいない……)
- ErlangとElixirで使われているBEAM VMを元にしたスケジューラーと、Effect Manager
The Elm ArchitectureのレイヤーはTaskから本質的に変わっていなくて、めんどくさい副作用の管理を下(現在Effect Manager)にまかせて、Cmd/Subを使うだけでよい。となっています。
今回3番を追ってみました。
##終わり、個人的な感想
- Elmは0.17でFRPをやめて、裏側Actorになった。Core/Processの項を読むと、将来的にはErlangスタイルの並行システムが良いのではとスケジューラーへ採用したらしい。Elmは技術選択が面白いなぁと思う。
- Msgが出入力を運ぶ。MsgをmapしてTEAを大きくする。TEAはとても静的(だからちょっと想像と違ったり)。Elmが採用していたFRPも、Rxのようなデータストリームではなくて静的なもので、ここは変わっていない。
- 裏側、結構シンプルで疎結合になっている。
- virtual domと、ActorとEffect Managerがあれば、The Elm Architectureを他の言語でも出来るかもしれない。でも純粋で、ある程度制限のあるElmと組み合わせてこそ、な気もする。
##まとめ
- ElmのスケジューラーはBEAM VMを元にしたスケジューラー
- Cmd/Subは集められ、それをみてeffectManagerはeffectを管理する。なのでこまめにCmd/Subをnoneにするのは有効。
- 下のレイヤーが変わっても、ユーザー側にあたるThe Elm Architectureのレイヤーは変わってない。