横田祐

swiftやkotlinなど、最新の言語はコンパイラ型チェックが厳しい。Windowsみたいにvoid *使えたら楽なのに。プログラミング言語の窮屈な檻から出る方法はこうだ。

テキストは文法や構文解析を無視できるから、トランスパイルすれば型チェックやその言語の制約や文法を回避できる。構文木をテキストに直して、もう一度、別種の構文解析すればなんでもできる。

iOSやmacOSは継続を陽に扱っている。普通IPの値は命令語長の分ずつインクリメントされていくので、プログラマが気にすることではない。だが、命令語長の分だけ自動でインクリメントされないCPUも考えられる。おそらく、アップルのCPU、Aシリーズ、Mシリーズはそれに該当する。IPがインクリメントされなければ、無限ループに陥るのではないか?そうではない、IPを変える別の機構があればよい。つまり、通常関数呼び出しの時にリターンアドレスをセットするように、ある纏まった処理単位(以下アトムと呼ぶ)ごとに次に実行するアドレス(以下ネクストと呼ぶ)をメモリ内に埋め込んで、なおかつ、各アトムが処理を完了するごとにそれぞれのネクストをIPにその都度セットするのである。一見、できることは普通のCPUと同じに見えるが、そうではない。ネクストは実行途中にシステムが簡単に書き換えられるのである。そうすると、継続を明示的に扱うことができる。つまり、現実的なcall/ccの実装に当たる。また、ネクストの参照数を数えれば、アトムのガーベージコレクションを簡単に実装できる。普通のCPUのようにスタックを使っていないことは明らかである。別の見方をすれば、C言語でいう関数は存在しないが、すべてコールバックなのである。

Array.mapのチェーンについて、コア毎に分割統治して全通り計算することで透過的にパイプライン処理できる。つまり、macOSやiOSは古典的なデータフロー計算機であると考えられる。

コア毎の分割統治の図、全体としてパイプライン処理できている。

なお、関数型言語にも言えることだが、共有メモリ型のデータフロー計算機は大量のメモリコピーを必要とする。メモリI/Oの速度がボトルネックとなるだろう。ただし、2つ前の演算結果を引数に取るような(時間的に)複雑な依存関係がなければ、極めて高速に動作するだろう。2つ前や3つ前の演算結果を引数に取る場合、マニホールドのように複雑なバッファ(例えば、swiftUIの@Stateや@ObservableObjectなど)を用意するか、プログラムを分割して並び替えてコンテキストを変える必要がある。マニホールドはシステムのウォッチドッグによって監視される。しかし、マニホールドを変更した場合の、オブジェクトの再計算やViewの再描画の順序はやはり複雑である。正しくマニホールドを扱わないと、再計算や再描画がループになってしまう。swiftUIの宣言的プログラミングは、swiftUIフレームワークの制約から来る。時相論理の強いUntilがマニホールドの効果である。マニホールドの変更による再計算・再描画は、本来なら、夫々1回ずつである。swiftUIのViewのidメソッドはIDを設定するのではなく親オブジェクトの再描画をidメソッドの引数という名前で依頼するのである。idメソッドの引数は親オブジェクトへのポインタと共にタイマー(またはメッセージループ)のキューに入れられ、再描画されたら、そのidメソッドの引数は無効化される(無効化リストに入れられ、もう一度設定しても再描画されない)。実はswiftUIの内部はとても手続き型であることがわかった。

AppleがPDFkitをswiftUIに移植しないのは、PDFが手続き型言語であるから。PDFは文書だけならswiftUIに移植できるが、スクリプトなどインタラクティブな側面もあるからswiftUIに移植できないのだろう。手続き型言語で関数型言語を設計できる。しかし、関数型言語で手続き型言語を設計できない。なお、関数型言語で別の関数型言語を(写像として)設計することは可能である。ただし、ここで言う関数型言語は副作用が全くないか、限られた副作用しかないと仮定している。関数型でできることは手続き型でできることに比べて少ない。実は関数型言語は不要なのではないか?ソフトウェアの使い勝手に関する作り込みやチューニングは手続き型言語によるところが大きい。関数型は大量のコピーをばら撒いた。

AIの深層学習の層はswiftUIと同じマップとマニホールドで作ることができる。マニホールドは副作用だから、性能の足枷になるし、純粋な関数型言語では記述できない。純粋関数型言語で表せないアルゴリズムはループ量子論と矛盾する。

ソースコードから(実行可能な)プログラムをコンパイルすることは、実質的には、多次元配列から一次元配列にコンパクト化することと同じである。ただし、そのプログラムの実行時にメモリ内容が一次元配列から多次元配列に展開されることが要請される。