要約
動的振る舞いのモデル化には、単に関数型というよりも、純粋関数型の一つの適用と捉えられる「データフロー・プログラミング」としての理解が有用です。データフロー・プログラミングとは数値計算処理を関数型の概念で再構成したものです。データフロー・プログラミングの要は、「アルゴリズムのライブラリー化」、即ち「演算子のユーザー定義」だと考えます。これにより「処理の単位」を自在に拡張することができます。これにより、動的振る舞いを、計算式の拡張としてモデル化することができるようになります。(※とは云っても大きな目線でいえば、関数型のセマンティクスの内です。)
◇
先日次の記事を投稿いたしました。
上記記事に対して、「ある程度理解できてある程度おもしろかったが、何かがいまいちすっきりせず少し残念」という趣旨のコメントを複数いただきました。。
つきまして補足を試みますm(_ _)m
◇
復習
先日の記事の論旨を復習します。
(1) オブジェクト指向は、関連やencapsulationで静的構造をモデル化するのに有用である。
(2) しかし振る舞いはimplementation任せで、構造化プログラミング以上には何も提供していなかった。
(3) 振る舞いをモデル化するのに有用な何かがないだろうか?
(4) チューリングマシン由来の「手続き」(※状態遷移ベース)ではなく、λ計算由来の「関数」(※変換過程ベース)は有用なのではないか。
(5) 何故ならば、関数を用いることで、振る舞いも「単位処理の依存関係」という構造同様の形態で扱えるようになり、“見通しのよさ”を得られるからである。
おそらく、最も重要な最後の(5)が結局言いたいことは分かる気もするが理屈がよく分からないという状態になっていたのではないかと思いました。私自身、ある種の“かんじ”を掴んではいたつもりなのですが、表現しきれてないなという思いはありました。
「データフロー・プログラミング」
『「単位処理の依存関係」という構造同様の形態で扱えるようになり、“見通しのよさ”を得られる』という、この“かんじ”を的確に説明してくれる「データフロー・プログラミング」を見つけました。
上のWikipediaの記事を読んでいただければ、もうそれで十分かとも思いますが、要点(=自分の理解)を以下にまとめます。
・処理と処理の間を流れるデータの“移動”に着目して、データが次々と“移動”していく様子を有向グラフに表現したモデルである。
・数値計算処理に関数型言語の概念を導入するかたちで開発された。関数型と共通の特徴を持つ。
・手続き型においては、処理の実行順序が「コントロールフロー」として表現されるのに対比される。
手続き型では、プログラムコードの表面に記述されるのはコントロールフローです。そのコントロールフローの中で、実行時にのみ実行環境の中に出現する「状態」の変遷を追っていくことが手続き型の駆動原理です。状態の変遷はコード上明示的で無いものです。
対するデータフロー・プログラミングでは、プログラムコード表面に記述するのはデータの入出力の連鎖です。データフロー・プログラミングでは、逆に、そのデータ入出力をどういったタイミングや順序で実行するのかは、実行時にのみ実行環境が(如何に並列化するかなど含めて)判断し制御するのですね。データフロー・プログラミングでは、コントロールフローの方がコード上明示的では無い、という訳です。
なお、「データフロー・プログラミング」と云っても、(純粋)関数型の概念からの拡張や逸脱はないと理解します。サブセットというものでも無く、(純粋)関数型の一つの適用だと位置付けられると考えます。
関数型からデータフロー・プログラミングを見た場合
関数型の方からデータフロー・プログラミングを見てみます。データフロー・プログラミングを支援するためには、次の要件が必要です。
副作用無しで参照透過な、純粋関数型であること
大元のλ計算の理論では、元来純粋関数型だけが対象だったと記憶しますが、現代の実用関数型言語は必ずしも純粋関数型ではありません。しかし、データフロー・プログラミングに適用するのであれば、純粋関数型として用いなければなりません。
ちなみに、手続き型言語でも「副作用無しで参照透過」に書くことはできます。曰く「グローバル変数は避けましょう」、というやつです。「副作用」とは、大きく、実行環境の外部資源に対するI/Oの問題と、実行環境内部にて、関数/プロシージャー/“サブルーチン”などのスコープの外の変数に対するアクセスの問題とに分けられます。外部資源I/Oの問題はしばし棚上げしますと、つまるところ、“サブルーチン”のローカルスコープ以外の変数に依存するようなロジックは避けましょう、という話です。
であるならば、手続き型(=というか非関数型)でもデータフロー・プログラミングを支援できるのではないか?と思われるかもしれません。しかし、データフロー・プログラミングは『数値計算処理に関数型言語の概念を導入するかたちで開発された』という点を思い起こす必要があります。つまり端的に、四則演算のような計算式を、関数呼び出しの連鎖として再構成したような形態だと云えます。『データフロー・プログラミングではデータの移動を有向グラフとしてモデル化』しますが、その有向グラフがどんなものなのか考えるに、そういった関数型解釈された計算式の抽象構文木的なものに相当すると云えるでしょう。全体として、関数型のセマンティクスそのままです。
「アルゴリズム」の再利用
データフロー・プログラミングの表面に描かれるのはデータの“移動”の様子です。その時、その“結線”の部分には「再利用可能な単位(ユニット)としての処理」が存在しています。
手続き型では、コードにコントロールフローを次々と記述できるので、記述している処理がどういう単位(ユニット)で分割されなければならないか、ということは都度都度は考えないかもしれません。データフロー・プログラミングでは、処理は都度都度入力と出力を定義してあげないと、ほとんど記述することができません。定義するということは、処理は自ずと再利用可能な単位としてライブラリー化される、ということです。手続き型やその権化としてのOOでは、ライブラリーというと、モノ(≒データ)を中核に具体的な用途となるユースケースが定まったものになりがちですが、データフロー・プログラミングにおける処理のライブラリーは、入出力データが処理に対して必ず外部化されるので、抽象的なデータ処理アルゴリズムとして仕立てる必要が出てきます。
そのようなアルゴリズムをライブラリー化した例としては、それこそ関数型言語でのStreams系APIでよくある、filter、map、foldもしくはreduceといったところ。後はC++のSTLが先駆者的に有名でしょうか。
そして、アルゴリズムをライブラリー化するという特徴は、動的振る舞いのモデルが『「単位処理の依存関係」という構造同様の形態で扱えるようになり、“見通しのよさ”を得られる』ための必要条件だと考えます。
データフロー・プログラミング=演算子をユーザー定義して拡張していくようなプログラミング
最後にもう一段思考を進めます。「ライブラリー化されたアルゴリズム」を「ユーザー定義された演算子」と解釈します。データフロー・プログラミングは数値計算処理×関数型だという点を踏まえると、「ユーザー定義演算子」解釈は、まさに計算式をユーザーの手で拡張することができる、という話になります。
◇
<追記1>
「データフロー」のモデルは、リアクティブ処理系の処理モデル理解としても参照されています。リアクティブの文脈におけるデータフローの話は、以下の岡本氏(Twitter:@okapies)の資料に詳しいです。
※上記記事内のスライド、28〜48ページ目あたりを中心に参照してみてください。
本記事におけるデータフローとリアクティブ処理系におけるデータフローの関係について、いずれ考察してみたいと思いますが、現時点でひとこと云うならば、「エンタープライズにおける動的振る舞いを、本記事の云うように構築することで、エンタープライズのプロシージャーをリアクティブ化することができるようになる」というかんじだと思います。
<追記2>
というか、エンタープライズでデータフローといえば「DFD」です。こちらも現時点でひとこと云うならば、比較的シンプルに関心を置いているところの粒度に違いがあるだろうと思っています。DFDは「プロセスレベル」、本記事の云うところのものは「プロシージャーレベル」、です。こちらもいずれ考察してみたいと思います。
◆以上