関数型プログラミングで、キーワードとなるのは、
- イミュータブル(変更不能)
- 状態変数
です。
これらは字面でも明らかですが、
「変わる」というのが概念の根本にあります。
プログラミングを実用化するにあたって、
たとえば、SICPの例では、銀行口座のアカウント、
それからこういうブログ・コンテンツマネージメント・システム(CMS)、
TwitterやFacebookなどのSNS、それからゲームなどいろいろありますが、
時間遷移するアプリケーションを設計する際には、
まさに「変わる」のは、実世界の「時間」に呼応して「変わる」ということです。
計算機科学において、以上のような実世界の事象を計算機の中でモデル化するわけですが、
実世界と、一言でさらっと言うが、実世界とは一体何か?
あんまり深く考える人はいない、
少なくともプログラミングではそんなことまで考える必要はない、と考える人がほとんどです。
実世界とは何か?この根本には我々が普通にあると「錯覚」している「時間」がその根本にあります。
実世界を語るとき、観察者の意識と「時間」というのは避けて通れません。
観察者の意識、というのは、コンピューティングにおいては、ユーザの意識です。
SICPでも、プログラミングとは、根源的には認識論の問題となると語られているとおり、
きちんと我々は一体何をやっているのか?丁寧に、一切の誤魔化しなく考えを詰めていくと、
計算機科学は必ずここに到達せざるをえません。
計算機科学とは、数学のみで完結しているわけはなく、
コンピュータというハードウェアで、実世界というものをモデル化するのであるから、
これは当然のことです。
「時間」こそがプログラミングにおいて根源的な概念です。
実世界をモデル化する際、
「時間」の概念を軽視することによって、明らかになるいくつかの強固な思い込みがあります。
明らかになる、というよりかは、ほとんどの場合まったく考慮されていないので、
その思い込みについては無自覚です。
思い込みは、掘り下げるといくらでも出てくるのですが、
如何せん哲学の話なので、本稿では理解のハードルが低い「現実的」なものに絞って論じます。
- 物質世界とは、「時間」変化するミュータブルな存在である。
基本的にはこの素朴な世界観があります。
基本的にはこの素朴な世界観が、プログラミングするときに無自覚に援用されます。
ほとんどの場合、この素朴な世界観に疑義を挟むことなくプログラミングは設計されていきます。
関数型プログラミングは、イミュータブルな世界観なので、
この「思い込み」によって徹底的な齟齬が生じます。
そしてこれは「思い込み」であり、間違いです。
この「思い込み」は、当然我々が生まれ育った環境からの経験で醸成される、
素朴な世界観なのですが、物理学で数学をもって、世界を俯瞰すると、
ごくごく単純に、我々の世界はニュートン物理学の宇宙観で近似できて、
時間 t
における世界は、時間関数 f
をもって、
と単純に描写できます。
- 物質世界とは、「時間」変数をもって表現できるイミュータブルな関数の返り値である。
と、こうなります。
思い込みによる素朴な世界観ではなく、より理知的なこちらの世界観を採用することによって、
関数型プログラミングのイミュータブルな世界観と、イミュータブルな実世界は完全に統合され、
関数型プログラミングの真のポテンシャルを実用的なアプリケーション開発に利用することが可能になります。
以上が、大前提となる基本方針なのですが、
以上をほんとうに実装するには、多少の知恵が必要です。
イミュータブルな物質的な実世界とは、
スピノザの汎神論で語られるとおり論理の一つのモードにすぎないわけですが、
根本的な原理としてそれは「無限」です。
ところが、ここでその「無限」の実世界の中のごくごく一部の矮小な存在でしかない、
「有限」なリソースしか有しないコンピュータ(計算機)で、モデル化するのだから、
結構、いろいろ無理です。
結構、いろいろ無理だから、この、
関数型プログラミングのイミュータブルな世界観と、イミュータブルな実世界は完全に統合
というアイデアは水泡に帰す、というのが繰り返されます。
だいたい考えてみればわかりますが、
有限に無限を押し込めることは土台不可能です。
無限はトリッキーで、アンタッチャブルです。触ってはいけないし、触ることはできない。
もちろんこれは、無限まるごと、という意味で、
無限の一部ならば、原理的に有限になり、有限ならば触れるようになる。
円周率の桁は無限に続くが、我々が計算するときは、
円周率の近似値をもって、計算します。
自然数も無限数列だが、自然数のひとつひとつは普通に触れる。
「論理」と「計算」は別である、ということです。
無限という「論理」はけっして「計算」しきれない。
しかし、
「必要な時に必要な分だけ計算する」のは可能です。
これは、プログラミングでは、イベント駆動であり、遅延評価です。
この双方は根本的には、同一の概念です。
これは、つまり前回でもSICPから引用した、
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-24.html#%_sec_3.5.1
http://sicp.iijlab.net/fulltext/x351.html
3.5.1 Streams Are Delayed Lists
3.5.1 ストリームは遅延リスト
In general, we can think of delayed evaluation as demand-driven” programming, whereby each stage in the stream process is activated only enough to satisfy the next stage. What we have done is to decouple the actual order of events in the computation from the apparent structure of our procedures. We write procedures as if the streams existedall at once” when, in reality, the computation is performed incrementally, as in traditional programming styles.
一般に遅延評価は 「要求駆動」プログラミングと考えることが出来る. ストリーム処理の各段階は次の段階を満足させるのに十分なだけ起動される. これまでやったのは計算における事象の実際の順を, 手続きの見かけの構造と 切り離すことである. われわれは伝統的なプログラミングスタイルでのように, ストリームがあたかも「みんな一緒に」存在したかのように手続きを書くが, 実際は計算が漸進的に実行される.
ということになります。
遅延評価とイベント駆動は、
「必要な時に必要な分だけ計算する方法」
と、拙書であえて表現しているとおり、根源的に同一概念であり、
「無限」の実世界の中のごくごく一部の矮小な存在でしかない、
「有限」なリソースしか有しないコンピュータ(計算機)で、モデル化するための唯一の賢い方法です。
その果実として、
関数型プログラミングのイミュータブルな世界観と、イミュータブルな実世界を完全に統合
することが、はじめて可能になるわけですね。
また、パラダイムとして、関数型プログラミングは宣言型ですが、
宣言型ということは、ソースコードの「論理」の記述は、
即座に「計算」されない、ということです。
「論理」が「計算機」によって読み込まれた「時間」に
「計算」されるのが、命令型プログラミングです。念の為。
拙書では、この評価戦略を「手当たりしだいの計算方法」とか書きました。
宣言型のコードは、「論理」が読み込まれた「時間」に「計算」されないので、
これは、遅延評価であり、同じ概念として、イベント駆動である、ということです。
だから、評価戦略でいろいろ、理解不能なより好みを強弁する連中もいますが、
関数型プログラミングと遅延評価は絶対に切り離せません。
ここは基本中の基本です。よろしいですね?
「必要な時に必要な分だけ計算する方法」
このスマートな絶対方針は、
関数型プログラミングのイミュータブルな世界観と、イミュータブルな実世界を完全に統合
するとき、あらゆる局面で、超強力な唯一の解決策になります。
時間 t
における世界を、時間関数 f
をもって、
と単純に描写するとき、
まず普通は、
たしかにイミュータブルだよね、しかしまあ、
こんなとんでもない時間関数を有限なリソースしかないコンピュータで実装するのは不可能である
そう思うわけです。
これは要するに、前述のとおり、「無限」と「有限」のことをよくよく検討しないで、
不用意に考察するから、そういうことになるわけで、
いかに必要ないと思い込んでいる哲学的考察でも、ちゃんと掘り下げておいたほうが良い、という教訓です。
もちろん実際は、「必要な時に必要な分だけ計算する方法」で解決可能な問題にすぎません。
- 物質世界とは、「時間」変数をもって表現できるイミュータブルな関数の返り値である。
を実装するということは、すなわち、
時間 t
における世界を、過去、現在、未来のすべての時間にわたって、もれなくすべてのデータを保持するということです。
たとえば、前回、こんなものは関数型プログラミングのコードではない、「ダサい」と批判した題材の、
「お絵かきロジック」ですが、
- 物質世界とは、「時間」変数をもって表現できるイミュータブルな関数の返り値である。
この世界観を、そのまま関数型プログラミングの世界観でコーディングする、ということは、
時刻 t
における、マウスポインタの座標を返す時間関数を実装する、という事になります。
アプリケーションが起動させてから終了させるまでの「すべてのあらゆる時刻」
t
における、マウスポインタの座標
をもれなく保持する、ということになります。
時刻 t
における、マウスポインタの座標
これは物質世界を俯瞰した理性的な世界観のイミュータブルなデータです。
そして復習ですが、非理性的な素朴な世界観では、「ダサい」コードでは、前回書いた通り、
Let’s step back and review where this complexity comes from. In an attempt to model real-world phenomena, we made some apparently reasonable decisions: We modeled real-world objects with local state by computational objects with local variables. We identified time variation in the real world with time variation in the computer. We implemented the time variation of the states of the model objects in the computer with assignments to the local variables of the model objects.
少し後戻りして, この複雑さがどこから来たか反省しよう. 実世界の現象をモデル化しようとして, 明らかに合理的な決定をいくつかした: 局所状態を持つ実世界のオブジェクトを, 局所変数を持つ計算オブジェクトでモデル化した. 実世界の時間変化を計算機内の時間変化と同一視した. モデルオブジェクトの状態の時間変化を, 計算機内では, モデルオブジェクトの局所変数への代入で実装した...
実世界の時間変化を計算機内の時間変化と同一視した
モデルオブジェクトの状態の時間変化を, 計算機内では, モデルオブジェクトの局所変数への代入で実装した.
秀逸な知見です。
状態変数への破壊的代入を繰り返すことで、そのモデルの時間変化に対応させている。
破壊的代入、っていうのはモデルの時間変化のためにやっていた、
こういう「自覚」をもってるプログラマって今、日本に何人くらいいるんでしょうか?
という感じになっていました。
時刻 t
における、マウスポインタの座標、というのは実際に、
pointer[t]
みたいに逐一残らず記録していっても構いません。
今どき大したデータ量にもならないでしょうから。
でもこのお絵かきケースもそうですが、多くのケースで、ほんとうに必要なのは、
直近の時刻t
における、マウスポインタの座標、だけですよね?
直近以前の時刻のデータについては必要ない。保持する必要はありません。
だから、過去のデータについてはどんどん破棄していけば良い。
時間 t
における世界を、時間関数 f
をもって、
の返り値を実装する、
時刻 t
における、マウスポインタの座標を返す時間関数を実装する、
にしても、関数の設計としては、関数内部で状態変数を保持して、破壊的代入して、
常に最新のマウス座標を返せば、それで事足りる、ということです。
実際こうやって設計されているのが、Facebook-Reactです。
Reactは真に宣言的なコンポーネント指向の関数ライブラリであり、
すべてのコンポーネントはイミュータブルであるわけですが、
Reactのコンポーネントにはstate
という、わかりにくい「状態変数」ぽい概念があります。
ぶっちゃけこれは、直近の時刻t
限定の時間関数です。
また、このブログのReact 解説【動的HTML-FRP編】
で、利用している、私が作った、worldcomponent The tiny FRP
も、以上の設計思想で構築されています。
これは、Reactコンポーネントのstate
のように、埋め込みFRPでない、
単体のFRP機構なので、いくらでもポテンシャルが見込めます。
ただ、それはそれ、として、
時間 t
における世界を、時間関数 f
をもって、
の返り値を実装する、
というのは、興味深いアイデアであり、
実際に、任意の時刻t
で、その瞬間の値を返す時間関数を実装するのは可能なのか?
概念実証もしました。
いろいろ検討しなければならない要素はあって、
まず、任意の時刻t
というものの「解像度」ですね。
時間っていうのは物理学でも、プランク時間の解像度しかなくて、デジタルで離散的だということですが、
コンピュータでは、マイクロセカンドの「解像度」が妥当でしょう。
じゃあ、マイクロセカンドの解像度で、たとえばマウスポインタの座標を全部保持するのか?
もちろんそういう設計も可能でしょうが、「必要な時に必要な分だけ計算する方法」でやればよい。
マウスポインタが移動したイベントの、そのタイムスタンプで、配列にデータを格納する。
マイクロセカンドの解像度で、すべてのタイムスタンプにデータを保持するのではなくて、
「差分」が発生した瞬間のデータのみを保持する。
だって、マウスポインタが移動していない、差分が発生していないのに、
マイクロセカンドの解像度で、すべてのタイムスタンプで座標データ保持とかやるのは明らかにスマートな方法ではない、「必要な時に必要な分だけ計算する方法」でやればよい。
逆に任意の時刻t
で、時間関数にアクセスしたときは、
その、任意の時刻t
から、「過去」の方向、タイムスタンプの値が小さい方向に、だーっとスキャンしていって、保持されているデータにヒットすれば、それが、任意の時刻t
に紐付けられるデータです。
なぜならば、差分がそのタイムスパンは発生していないのだから。
これが拙書で、
timecomponent タイムコンポーネントで時間軸上の全部のバージョンを扱う
と紹介している、
https://www.npmjs.com/package/timecomponent
であり、
timecomponent タイムコンポーネントで過去にアクセスし、1秒前の過去をリアルタイム再生する
と、実際に概念実証のDEMOを示しています。
http://sakurafunctional.github.io/demo/frp-redball-delay/index.html
上記URLクリックして開いて、マウスポインタ動かすと、1秒前の過去に「リアルタイムアクセス」することができます。
で、実際この、
すべてのタイムスタンプにデータを保持するのではなくて、
「差分」が発生した瞬間のデータのみを保持する。
というアイデアは、結構使えるんですね。
というか、とっくに広く使われています。
GitHubです。
Gitのようなバージョン管理システムは、データの差分のみを記録していきます。
さて、これはどういう意味か?
- 物質世界とは、「時間」変数をもって表現できるイミュータブルな関数の返り値である。
関数型プログラミングのイミュータブルな世界観と、イミュータブルな実世界を完全に統合
時間 t
における世界を、時間関数 f
をもって、
の返り値を実装する方針において、
データベースを、関数型プログラミングでイミュータブルに実装する手段をすでに我々は持っている、ということです。
あらゆる、膨大なデータベースを、差分バージョン管理システムを適用することによって、
任意の時間t
におけるデータベースを、イミュータブルに、参照透過に参照できる、ってことです。
そもそも、Gitのようなバージョン管理システムという仕組み自体が、
時間を実世界のデータのバージョンインデックスとして取り回すパラダイムなんですが、
もういろいろこのように、道具立ては揃っているわけです。
道具立てだけは揃っていて、そのパラダイム、世界観、設計思想、考え方に追随できていないのが世のプログラマです。
関数型プログラミングのイミュータブルな世界観と、イミュータブルな実世界を完全に統合
するにあたって、このパラダイムが、
実世界の時間変化を計算機内の時間変化と同一視した
モデルオブジェクトの状態の時間変化を, 計算機内では, モデルオブジェクトの局所変数への代入で実装した.
@SICP
という、オブジェクト指向のパラダイムと対立するのは、当たり前のことであって、
「時間」と世界観と、当然の哲学の話を語れば、宗教の勧誘、人智を超えた形而上学的な幻想世界、オカルト扱いされる現状は、ダサい、劣ってるなあ、と思うわけです。
0 コメント:
コメントを投稿