関数型と比較しつつ
関数型プログラミングにおける式は、値と値の関係を表します。
一方リアクティブプログラミングでは、式は、振る舞いと振る舞いの関係を表します。
純粋な関数型プログラミングでは、参照透明であること、つまり式を値で置き換えてもプログラムの意味が変わらないことが求められます。
変数の再代入を許すと、変数や式を値で置き換えたときにつじつまがあわなくなってしまいます。
なので純粋関数型プログラミングでは変数の再代入を許さないわけです。
一方、リアクティブプログラミングでは振る舞いを扱いますが、
振る舞いも、ある一瞬を切り取れば、ただの値とかわりません。
振る舞い同士の関係は、ただの値同士の関係であり、式は参照透明なものになっています。
そして、時間がたって振る舞いが変化した場合は、値を再計算して他の振る舞いを更新することでつじつまを合わせるわけです。
リアクティブプログラミングでは、この更新の度に再計算するという単純なアイデアにより、宣言的な式により状態の関係性を記述することが可能になるのです。
とは言っても、実際にすべての値を再計算していては時間がかかって仕方ないので、いくつか工夫をします。
リアクティブプログラミングとは
まず次のような定義を行います。
a = 1
b = a * 3
そしてaに2を代入してみます。
a = 2
そしてbを出力してみましょう。
通常の命令型言語であれば、この結果は、
b -> 3
となります。
しかし、リアクティブプログラミングでは、
b -> 6
となります。
b = a * 3
の意味が、命令型プログラミングとリアクティブプログラミングでは異なるわけです。
命令型プログラミングでは、そのときのaの値である1を用いて、
b = a * 3
b = 1 * 3
b = 3
という意味だと解釈され、bは3に束縛されます。
そのあとaの値が書き換えられても、bは変わらず3のままです。
一方、リアクティブプログラミングでは、
b = a * 3
は、
「bは、常にaの3倍である」
という意味になります。
時間がたち、aの値が変化しても、
b = a * 3
という関係は変わりません。
この一見奇妙な振る舞いは、日常の感覚からすると、実は普通だったりします。例えば、「温度計のさす目盛の位置は、温度の関数になっている」と言ったとき、温度によって目盛の位置が決まる、温度が変化すればそれに合わせて目盛りの位置も当然変化します。身長と体重からBMI値が求まるなら、身長や体重が変化すればBMI値も変化するのは当然です。
こういった関係のように、時間が経過し状態が変化しても一定して変わらない関係に着目するのがリアクティブプログラミングだともいえます。
ところで、なぜ重要なのか
Why Reactive Programming Matters.
えーと、リアクティブプログラミングがなぜ重要なのか、というのが本題でした。なので、実はここからがメインです。
なぜ重要だと思うのか書かなければいけません。その理由について、モジュール性の向上と状態管理の自動化という点から見てみたいと思います。
モジュール性は大事
「関数プログラミングはなぜ重要か」によれば、
関数型プログラミングの利点は関数のモジュール性の高さによるものです。
そして関数のモジュール性の高さを遺憾なく発揮するために、副作用が無いことや遅延評価であることが必要なのだと。
リアクティブプログラミングも同じです。副作用を含むシステムをうまくモジュール化できる可能性があるから重要なのです。
構造化プログラミングからオブジェクト指向や関数型プログラミングへの進化の歴史は、
モジュール性の向上の歴史でもあり、同時に「変更可能な状態」との戦いの歴史でもあります。
構造化プログラミングは、制御フローをモジュール化することに成功しましたが、
その後に「グローバル変数」の問題が残されました。
制御フローがモジュール化されてもデータがモジュール化されていなかったのです。
そこでオブジェクト指向は、データと手続きをまとめてモジュール化しました。
そこで残った問題は「変更可能なオブジェクト」のモジュール性は、実はそんなに高くないということです。
オブジェクト指向の問題は、状態をオブジェクトに閉じ込めても、各オブジェクトが持つ状態間の相互作用をゼロにすることはできないことです。
状態を持つシステムとやりとりを行うプロトコルの設計を考えればわかりやすいですが、相手が状態をもつ以上、それとやりとりする側も
相手の事情、つまり今どんな状態なのかをまったく無視して話しかけるということはできないのです。
一方純粋関数型プログラミングでは、副作用を排除した関数をモジュールとすることで、高いモジュール性を得ました。
しかしそんな純粋関数型プログラミングも外部とやりとりをして仕事をなすためには、
その強力な抽象化能力をもって手続き的な処理をシミュレートせざるを得ませんでした。
こう見ていくと、オブジェクト指向は副作用の分割統治を、関数型プログラミングは副作用の排除を行うことで、
モジュール性を高めようとしたと考えられます。
リアクティブプログラミングでは、値を直接扱うのではなく、
「時間とともに変化していく値=振る舞い」を扱うことで、関数型プログラミングのような宣言的な記述で
変化する状態をもつ系を記述できるのです。
変化する状態を扱いながらもモジュール性が落ちないのは、状態が変化したときにそれに依存した状態が自動的に更新されるため、
状態の依存関係について意識する必要がないからです。
オブジェクト指向では、状態に依存関係がある場合、まず初期値をセットするロジックを書き、さらにObserverパターンなどを用いて
更新を管理し、さらに効率的な更新のために適切なキャッシュポイントの設定まで明示的に行う必要があります。
リアクティブプログラミングでは、
更新の管理やキャッシュポイントの設定は自動的に行われるため、
プログラマの仕事は振る舞い間の関係性だけを宣言的な式で書くことだけになるのです。
自動化も大事
モジュール性の向上と並んで重要なのが、自動化による利点です。
これはGC(ガベージコレクション)とのアナロジーで考えると分かりやすいと思います。
GCは、メモリの管理を自動化してくれるため、メモリの確保・解放を明示的にする必要がなくなります。
これによってコードの記述量が減らせるのはもちろんですが、もっとも重要なのはメモリリークや無効なポインタに関するバグなどの、
メモリ管理にまつわるバグを削減できることです。
手作業によるメモリ管理は手間がかかるだけでなく、バグの温床にもなりかねないわけですが、一度メモリ管理を自動化する仕組みを作ってそれに任せてしまえば、
場当たり的に実装するよりもバグを減らすことが可能だという理屈です。
これと同じことがリアクティブプログラミングにもあてはまります。
Observerパターンによる状態の管理は、バグを生み出しやすいことで知られています。
状態の更新管理を手作業で一つ一つ指示していくため、メモリ管理と同じく、
抜け漏れがあると特定のパターンでのみ矛盾した状態を招く厄介なバグを生み出す可能性があります。
なので状態の変更を管理する仕組みを作ってそれに任せてしまえばよいというのがリアクティブプログラミングの考え方だともいえます。
なぜリアクティブプログラミングは重要か。
リアクティブプログラミングは、「時間とともに変化する値」=「振る舞い」同士の関係性を記述することでプログラミングを行うパラダイムです。
GUIなどのようにインタラクティブなシステムや、シミュレーションやアニメーションのようにダイナミックに状態が変化するようなシステムを宣言的に記述することができます。
これらの「変化する状態」や「外部とのやりとり」が支配的なシステムは、純粋関数型言語が、その強みを発揮しにくい部分でもあります。
本稿では、リアクティブプログラミングが副作用を含む系を宣言的に記述することを可能にし、状態の管理という厄介な問題からプログラマを開放する可能性があることを示したいと思います。
(割と独自研究に基づく解釈ばかりなのでその点ご了承ください。あと例としてでてくるコードは、Pythonベースの擬似コードで具体的なライブラリに基づくものではありません。)
「属性と関係」のつづき
今回は、 http://togetter.com/li/71943 で話した属性と関係の話のつづきです。
オブジェクト指向やリレーショナルモデルでは、属性という概念が登場します。属性という用語/概念は、プログラマにはなじみ深いものです。あたりまえすぎて、もはや空気のような存在かもしれません。
この属性というものを使わずに、別の「関係」というものを使う関係指向という考え方について紹介します。*1
http://togetter.com/li/71943 では、OOとの比較、RDBとの比較、実装よりの話などが混在してしまいました。
そこで今回は、オブジェクト指向との比較に絞って話をします。*2
オブジェクト指向と関係指向の違い
クラスベースのオブジェクト指向では、まずクラスを定義します。クラスの定義には、属性の定義が含まれます。(メソッドの話は今回はしません。)そしてクラスから生成された各インスタンスは、クラス定義に基づいた属性を持ちます。
図にするとこんな感じです。(図はイメージです。)
オブジェクト指向では、属性値としての数値や文字列などの値もオブジェクトであると考えます。あるインスタンスが、他のオブジェクトを属性として「持つ」、あるいは、他のオブジェクトを知っているなどと表現します。
「持つ」にしろ、「知る」にしろ、属性に関する情報はオブジェクトの中に「含まれる」ことになります。
一方関係指向では、会社員や数値の集合があり、それらの集合の間に名前や身長などの関係があります。
図にするとこんな感じです。
会社員の集合に含まれるのは、他と区別することができる点としての会社員エレメントです。
先程のオブジェクト指向の例の会社員インスタンスとちがい、この会社員エレメントは、属性等の内部構造を持ちません。会社員エレメントは、識別するための情報を持っていれば良いので、01や02などのID番号を振ります。
各会社員がどんな名前であるか、身長がいくつであるか、といった情報は、名前という関係や身長という関係が持ちます。関係は、始点となる集合、終点となる集合の間を結ぶものとして定義されます。そしてその内部に、始点となる集合の要素と、終点の集合の要素の関係づけを持ちます。
「関係」が要素の対応関係の情報を持つという点を強調して図にするとこんな感じになります。
この「関係」は、始点となる集合の要素がひとつ決まれば、終点となる集合の要素が複数個(0個を含む)きまるもので、プログラミングでいう関数や連想配列に近いものです。この関係の正確な定義はまた別の機会にします。
各集合の要素は、文字列やIDといった単なる値であり、オブジェクト指向のインスタンスのような内部構造を持ちません。この点がオブジェクト指向と大きく異なるところです。
オブジェクト指向においては、すべてがオブジェクトであるということがよく言われますが、関係指向では、関係と値の集合という2週類を別のものとして扱います。
オブジェクト指向は一元論であり、関係指向は二元論であるといえるかも知れません。
まとめ
オブジェクト指向というパラダイムで、自分自身を見ると、名前や血液型など、様々な属性を備えた存在であるということになります。これらの属性情報は、各人が「持っている」「備えている」「知っている」ものであると考えます。
一方関係指向という見方で自分自身を見ると、ただアイデンティティだけが自分自身であり、名前や血液型というものは自分の内部にではなく、外部の関係性のなかにあるということになります。そして例えばこの人は田中さんで、あの人は鈴木さんで...と各人に文字列を対応づけるものが、名前という関係であるということになります。血液型であれば、各人をA型やB型といったものに対応づけるのが血液型であるということになります。
もちろんここでの目的は哲学的な話をすることではなく、より宣言的な記述で効率よくプログラミングをするためのモデルやそれに基づく処理系を構築することです。関係指向という見方も、目的ではなく手段です。この見方をもとに、どのようにアプリケーションの記述を行うかということが本題です。その本題にはいる導入として、まずはオブジェクト指向との見方の違いに絞って書いてみましたが、いかがでしたでしょうか。
ご意見ご感想ツッコミ等お待ちしております。<追記>
書いてみて思ったのですが、やっぱりこれだけ書いて終わりでは、なんのこっちゃだと思うので、全体の経緯や目的の話、実装よりの話、Reactive Programming関係の話、RDBとの対比、圏論との関係の話なども需要があれば書きたいと思います。追記>
ContextureDB(下書き)
はじめに
ContextureDB(略してCtDB)は、
関数型データモデルをベースにReactive ProgrammingやHaskellのArrowを参考に拡張を行ったDBモデルを持ち、
アプリケーションロジックの宣言的な記述やツリー状データによる入出力を特長とするDBMSです。
目的は、Webアプリケーション開発の省力化の実現です。
(パフォーマンスやスケーラビリティの向上は、現段階では目指していません。
計算量が時間的、空間的に、RDBMSを使用して開発された典型的なアプリケーションと同じオーダーであることを目標としています。)
関数型データベースについて
関数型データベースでは、値の集合と関数でデータを表現します。
例えば、社員IDと社員名、社員の年齢に関するデータを表現する場合、
RDBでは次のようになるでしょう。
社員ID, 名前, 年齢
1, 中野, 30
2, 藤原, 40
...
関数型データモデルでは、
次のようなルールにもとづき
社員IDをとり、社員名を返す関数と、
社員名 :: 社員ID -> 名前
1=>中野
2=>藤原
次のようなルールにもとづき
社員IDをとり、社員名を返す関数と、
社員年齢 :: 社員ID -> 年齢
1=>30
2=>40
で表されます。
このような関数は内部的にハッシュやbtreeのようなデータ構造をもっており、
当然変更することができます。
利点は、データとロジックを関数として統一的に扱う事ができるという点です。
関数型データモデルの歴史
関数型データモデルは、非常に古くからあり、最初のfunctional datamodel であるDAPLEXは
70年代につくられたようです。
The first functional data model DAPLEX was developed at MIT in the 70s
Reactive Programming
データフローや更新を伝播する仕組みに関するプログラミングパラダイムです。
a = 10
b = a * 2
a = 15
print b
このように、bがb = a * 2として定義されているとき、
aが変化したときに自動的にbの値を自動的に再計算して更新する仕組みをReactiveと呼びます。
Excelのような表計算ソフトが、一般的な例としてよく揚げられます。
http://d.hatena.ne.jp/maoe/20100109/1263059731
http://en.wikipedia.org/wiki/Dataflow
CtDBについて
CtDBは、値の集合であるObjectと、集合と集合を結ぶ関数であるArrowによってデータを表現します。
(用語は圏論とは直接関係ありません。)
Object
Objectには型があります。Integer型のObjectはInteger値の集合であり、String型のObjectは、
String値の集合です。
Identiry型のObjectというものもあり、これは正の整数で表されるIDの集合になります。
Arrow
Arrowは入力元のObjectと出力先のObjectを持ちます。
Arrowの型は入力元のObjectと出力先のObjectにより決まります。
Base Object
Objectの合成してObjectを生成する事や、
Arrowの演算から別のArrowを生成する事ができます。
一方、合成などの演算により生成されたのではない、基底となるObject/Arrowを
Base Object/Base Arrowと呼びます。
BaseであるObjectは、集合である自分の要素をデータを持ちます。
たとえば、
A = {1, 2, 3}
B = {3, 4, 5}
C = A | B (= {1, 2, 3, 4, 5})
のように、Aが、整数1,2,3を要素としてもつ集合、
Bが、整数3,4,5を要素としてもつ集合であり、
CがAとBの和集合として定義されるとき、
AとBはBase Objectであり、CはBase Objectではありません。
また、CがこのようにAとBの和集合として定義されるとき、
AやBの内容が変更されたとき、自動的にCの内容も更新されます。
たとえばBの要素として整数の6が追加された場合、同時にCも新たに6を要素としてもつことになります。
A = {1, 2, 3}
B = {3, 4, 5, 6}
C = A | B (= {1, 2, 3, 4, 5, 6})