ビジュアルプログラミングと関数型
ビジュアルプログラミングと、逐次実行される命令文列としてのプログラミング言語の食い合わせは本質的に悪いという話。
副作用を持つ文は実行順が重要。例えばp5.jsの関数やOpenGLのAPIは、それぞれが描画コンテクストの状態を変更するコマンドであるため、関数の呼び出し順は正しく保証される必要がある。したがって以下の文は並び替えることも、並列化することも出来ない。
code:js
fill('red')
circle(50, 50, 100)
fill('blue')
rect(10, 10, 80, 80)
に対して、GlispやPave.jsの設計思想は、それぞれの関数はただ値を返すのみで、それ自体が何か副作用を伴うアクションを実行することはないということ。上記のスケッチはこうなる。 code:clj
(g
(style (fill "red")
(style (fill "blue")
それぞれの関数は図形やスタイルを表す値を返す。そうして再帰的に呼び出され、値が合成され、最終的に g (グループ化)関数が返す値がグラフィック全体を表す静的なデータ表現となる。それを実際にキャンバス上に描画するという副作用を担うのはアプリケーション側であって、ユーザーが呼び出すことの出来る関数は純粋である。
このような設計のメリットは、1. コードが宣言的になる。つまり、How 「どうグラフィックを描くかという手順書」ではなく、What 「グラフィックがどういった構造をしているのか」を自己説明する、ちょうどSVGのようなドキュメントとなる。そして 2. 実行順を問わず、並列化が可能となる。特に後者は、テキストのような線状性をもったメディアではなく、ディスプレイ上に平面的に展開されるビジュアルプログラミング、部分式をユーザーがリアルタイム更新するライブプログラミングとの相性が良い。(要出典)
ノードベースUIと実行順
ツリービューやノードベースUIは、2次元的でノンリニアな拡がりを持つために、テキストのような1次元的な表現と異なり、実行順がデータ表現から自明ではない。そのために、ノードUIの各ノードが副作用をもつ場合、何らかの形で実行順を明示するためのUIが必要となる。例えばQuartz Composerでは、青で示された描画ノードごとに、描画順を指定する。
https://scrapbox.io/files/66a11f2ad843e4001c44198f.png
Unreal Engineの場合、ノード間を結ぶ白い線が実行ピンを表す。上流のノードが何らかのイベントを発火すると下流へと伝播していき、「深さ優先」でノードが表すアクションが実行されていく。IfやSequence、Mergeといった、プログラミング言語でいう制御文に相当するノードを噛ませることで、実行順を保証する。
https://scrapbox.io/files/66a1201dd577c9001cd1d5b1.pnghttps://scrapbox.io/files/66a12073313f5a001d543fcb.png
ノードベースUIの意味論として、おもに以下の2つがあると思う。
フロー制御型(flow control)
上流から下流へと、ノード間をてデータや信号が流れ落ちるイメージ
順序やタイミングといった、時制感覚が存在する
イベント・ドリブン
音楽やゲームといった、リアルタイム性のあるツールに多い
例) Unreal Engine - Blueprint, TouchDesigner, Max/MSP
依存グラフ型(dependency graph)
式同士の依存関係を表す
静的なデータの構造を表す「図」に近いものであって、そこにプログラムとしての時制感覚はあまりない
RunではなくCookという表現を用いる
「実行」するのではなく、そのノードが依存する上流のノードの変更を受けて、再計算するというイメージ
目的のノードをCookするために必要な依存ノードを、下流から上流へと辿りながらCookする
グラフィックや3DCGなど、非インタラクティブな表現のためのツールに多い
例) Houdini, Substance Designer, Grasshopper
自分の仮説は、適切な表現力をもった依存グラフ型は、フロー制御型を内包するのではないかということ。というか、モジュール性やシンプルさ、GUIとの親和度を高めるには、依存グラフ型がより適していると考えている。そのためには、ビジュアルプログラミング環境は以下の機能をサポートする必要がある。
副作用を純粋関数型的な世界観に閉じ込めるIOアクション
「〜する」という動詞文を「〜すること」という名詞節に変換して考える
console.log そのものではなく、「console.logを実行すること」というアクション値として捉える
アクション値をフィルターしたり、合成するための関数がある
A, Bという2つのIOアクションを受けとり、「Aした後にBすること」
「状態」を関数型の枠組みで扱うためのStateモナド
関数オブジェクトやリストを値として扱える
for, while といった繰り返し文は、mapやfoldといったリスト操作として表現できる
ここまで書いて力尽きた…。
余談1: 順序機械モデルは果たして直感的なのか
プログラミングを学んで突っかかるのは、x = x + 1 という式の意味である。学校の数学なんかでは「右左辺の関係性を表す記号」だったはずの等号=が、「右辺を左辺に代入する操作」を表すということに気づくまでは、かなり面食らう。
...書きかけ
余談2: ノードUIと反復処理
余談までに、上記の機能を持たないノードベースUIがフィードバックや繰り返し処理を表現するのにいかに苦心しているかをペタペタ貼る。
Houdiniでは、For-Eachノードの間に挟まれたノードは複数回Cookされる。その際、foreach_endノードが受け取った出力を、次のイテレーションの入力に使うか、イテレーションごとに独立して計算してから最後にマージするかを選べる。
https://tokeru.com/cgwiki/assets/Forloop_move_sphere.29719f8b.gif
HoudiniのSolverは、「前のフレームの計算結果を参照して、次のフレームを計算する」というフィードバック処理全体を抽象化したノード。Prev_Frame × Input -> Output という、ある種の漸化式のような構造をもった内部ノードを持つことができる。パーティクルや物理シミュレーションといった仕組みを統一的に扱う。
https://scrapbox.io/files/66a12d69daad97001d6058d9.png
Quartz Composerでは、Iterator Variablesの下流に繋がれたノードは、一度のフレーム更新時に複数回呼び出される。
https://scrapbox.io/files/66a12c842da54f001d8858fd.png
Touch Designerでは、Feedback TOP用いて、任意のノードの最後の描画結果を次のフレームに持ち越すことが出来る。その名の通りフィードバックフィルターに使える。
https://i0.wp.com/farm6.staticflickr.com/5476/9061693910_a708ee26bc_z.jpg