読者です 読者をやめる 読者になる 読者になる

ReactiveX と「普通のやつらの上を行け」の意外な関係

これは「関数型プログラマのための Rx 入門」の補足記事です(タイトル変えた)。

前編後編とお送りしてきたこの記事だが、特に後編について「何を言ってるのか分からん」というコメントを何人かの方から頂いた。…なんというか、ごめんなさい

繰り返しになるが、Rx を使う上で関数型プログラミングの知識は必ずしも必要ではないし、むしろ(関数型のコンセプトが基礎にあるのに関わらず)知らなくても使えるようになっている。ライブラリの作者たちは「過度な抽象化は害になる」ということを弁えているのだろう。

しかし、Rx と関数型プログラミングの関係を把握しておくと、非同期データストリームのビルディング・ブロックの作り方について大いに視野が広がるだろう。もし、貴方がこの記事の前提となる「関数型」のパラダイムに興味をお持ちなら、まずは関数プログラミング実践入門」をお勧めしたい。

本の内容そのものは Haskell を前提にしているが、関数型の重要なコンセプトが一通り紹介されているので、今回の記事で出てきたキーワード(高階関数、代数的データ型、モナド、…)が属する世界観を概観するのに良いと思う。

また、このテーマに本気で取り組みたい初学者の方には、つい先日に発売されたばかりのScala関数型デザイン&プログラミング」を併せてお勧めしたい。

この本は、かねてより国内外で高い評価を得ている "Functional Programming in Scala" の日本語訳になる。ざっと見た感じ非常に「歯ごたえがある」感じだが、「関数型でプログラムを組み上げる方法」を基礎から丁寧に解説しており、演習問題も充実しているので、一冊読み通すとかなり力がつくのではないかと思う。

以下、後編を書いた後に気付いた話について少し補足。題して「Rx と『普通のやつらの上を行け』の意外な関係」

Observable の由来

後編で延々と書いたように、Reactive Extensions (Rx) の ObservableIterable の双対になっている。

() => (() => T)     // Iterable[T]
(T => Unit) => Unit // Observable[T]

では、形式的な説明はそれでいいとして*1、実際のところ Observable のアイデアはどこから来たのだろうか?

Erik Meijer の以下の投稿によれば、Observable は Rx の前身である Volta プロジェクト*2で非同期呼び出しをうまく扱う方法を探している時に見出したものだという:

We started working on IObservable/IObserver a long time ago when we were trying to make asynchronous calls that arose from tier-splitting palatable. Initially we used just the continuation monad but then discovered the beautiful duality with IEnumerable and IEnumerator.

F# vs. Rx: Msdn forums - Reactive Extensions (Rx)

そして、これは継続モナド (continuation monad) から着想を得たものであるらしい。*3

class Cont[R, +A](val runCont: (A => R) => R) { ... }

継続渡し形式の関数 (CPS function)

上記の継続モナド Cont が保持している関数 runCont: (A => R) => R継続渡し形式 (Continuation Passing Style; CPS) の関数という名前で呼ばれている。そして、後編で導出した Observable を表す「引数に渡されたコールバック関数に値を渡して実行する高階関数」も同様に CPS 関数だ(RUnit を適用してみよう)。

(A => R   ) => R    // runCont
(T => Unit) => Unit // Observable

継続渡しの「継続」とは、ここでは CPS 関数に渡されるコールバックを指している。CPS の詳しい説明は下記に挙げたページを見てほしいのだが、簡単に言うと「関数を呼び出して、戻ってきたら『続きの処理』を実行する」代わりに「関数に『続きの処理』を渡して呼び出し、その関数の最後で実行してもらう」というやり方だ。この「続きの処理」を継続と呼ぶ。

継続渡しで「普通のやつらの上を行け」

一般に「継続」は扱いの難しいプログラミングコンセプトとされていて、あまり積極的に活用されることがない(と思う)。では、なぜ非同期呼び出しの文脈で継続が出てくるのかというと、「処理をある所で中断してコンテキストを保存し、続きの処理が再開されるときに受け渡す」というパターンが CPS の考え方にバッチリはまるからだろう。以下、「なんでも継続」から引用:

ポイントは、外部の処理を呼びたいのだが、呼び出して戻り値を受け取るという形式が使えないケースにある。

例えばユーザインタフェースだ。処理の途中でユーザーに何か入力を促し、その結果を使って処理を続けたいことは良くある。しかし多くのGUIプログラミングでは、ユーザーの入力を受け付けるためには一度GUIのイベントループに戻らなければならない。したがって、プログラマは 処理をユーザーの入力の前にやる処理Aとユーザーの入力の後にやる処理Bに分けて、

 1. 処理Aの最後に入力ウィンドウをポップアップし、イベントループに戻る

 2. 入力ウィンドウの "OK" ボタンが押されるイベントが発生した時に 処理Bが呼ばれるようにする。

という具合にコーディングしているはずだ。この時、まさに処理Bは処理Aの「継続」なのだ。(Webアプリケーションにも全く同じ原理が使えることを指摘しておこう。 ユーザーからの入力が必要になった時、Webアプリケーションは一度入力フォームを吐き出してhttpサーバに制御を戻さなければならない。「普通のやつらの上を行け」Paul Grahamが述べているYahoo! Storeの システムはまさにこの技術を実装している。

つまり、歴史的にも意味的にも、Observable はまさにユースケースを非同期データストリームに絞って扱いやすくした継続モナドだということになる。

アカデミックな知識が MUST になる時代?

この「なんでも継続」の記事は(ブクマを見る限り)少なくとも 2006 年頃からあって、私も何度か目を通していた。のだが、正直なところ、今回記事を書くために読み返すまで UI や Web アプリへの応用という話は完全に忘れていた。

ぶっちゃけると、00 年代後半(?)にウェブ界隈で何度か不動点コンビネータの話がバズっていた頃、Yコンビネータの話題に関連してこの記事を読んでいるはずなのだが、当時は「いかにもギークの好きそうな頭の体操」として受け止めていて、応用の可能性についてはろくに考えが及んでいなかったと思う。

とかく、こうしたアカデミックな形式知は「小難しくて実用性がない」として軽んじられがちだ。しかし、関数型プログラミングしかり、こうして積み上げられてきた知見が実践的な問題に取り組むためのフレームワークとして活用されるケースは増えているし、海外の新しい OSS の動向を見るに、今後ますます増えていくだろうという感想を持っている。

全てのソフトウェア技術者がこうした方面の知識を習得すべきだとは思わない。しかし、個人的な感想としては、それぞれの現場で新技術の開発や選定に関わるリーダーや、エバンジェリストを自認するオピニオンリーダーにとって、このような学問的知識の習得が MUST になる時代はそう遠くない、という予感は日々強くなっている。

*1:ちなみに、Meijer は Rx の公開前にアップされた紹介動画の時点で既に双対性についての議論を披露している。この双対おじさん、筋金入りである…。

*2:Rx の電気ウナギのアイコンは、Volta のものをそのまま引き継いでいるらしい。

*3:Scala でのコード例は kmizu さんのコードから引用。