port

とりあえず、平行プリミティブとしてportを使うことにする。
これはpi計算や他のプロセス代数によく似ている。

一般化port

いわゆるR6RSのportはbinaryかtextしか扱えないため、一般化したportを考える。
これは要するに遅延評価されるclosureで、原理的にはclosureで実現できるはずだが、portは通常のclosureと以下の点が異なる :

  • 動作をキャンセルできる
  • multiplexできる(後述)
  • タイムアウトできる

このシステムをデザインする上で最も難易度が高いのはportを通るデータの値域を限定することにある。値域を限定しなければ、異なるVM間でデータをやりとりすることができなくなってしまう。
(このようなportは素のSchemeでも原理的には実装できる。単に実装効率のために独立してデザインしている。)

portfactory

一般化portを返すclosureをportfactoryと呼ぶ。例 :

例えば一般的な式をasync-evalに渡すとportを得る(もちろん現段階ではasync-evalなんてものは無い)。
非同期I/Oのために、port自身もportfactoryとなる。つまり、(async-read port) => portであり、返されたportをreadすることで初めて完了が保証される。
portに対する操作は大量に存在する。read, write, seek, tell, checkpoint, ...。R6RSのcustom portのような方式はすぐに破綻するだろう。

multiplexer

multiplexとは、複数のportを同時に待つ事を言う。multiplexerをportfactoryにするかどうかは微妙な問題といえる。
要するに、

(cond-parallel
   ((read socketA) ...)
   ((read socketB) ...) ...)

のようなプリミティブを用意するべきなのか、プリミティブで待てるオブジェクトは1つに限定すべきなのか。
例えば、FreeBSDのkqueueはportfactoryとよく対応している。multiplexer portfactoryを導入することで、待つたびにkqueueを作成/破棄する必要が有るよりは、明示的にkqueueを作成できる方が好ましいように思える。libeventのインターフェースもこちらになっている。
しかし、select()のように毎回何を待つか指示しなければならないようなケースではオーバーヘッドに違いは無い。

実装

いろいろ考えたが、今のところは、1threadにつき1portで行くことにした*1
libeventだと待てるオブジェクトと待てないオブジェクトが出てくる。例えばSSLライブラリを外部のライブラリを使って実装した場合、それがSSLストリームをfdに見せてくれるとは限らない気がする。もちろん、そのような場合でも専用のthreadを用意してpipeにwriteすることでfdに見せることが出来るが。。
あとlibeventはWindowsにおいてSocket以外を扱うのに不安がある。確かに今やWinSockのsocketはReadFileといった通常のI/Oルーチンで扱えるが、その逆、つまりファイルハンドルをWinSockのselect()で扱うのが裏技でない根拠を知らない。
futureやspawnのようなプリミティブがscheme(やLisp)では一般的につかわれるが、これらはpersistentでない  つまり、一旦値を返すと消滅するので、直接的に実現すると再生成の操作が必ず必要になる*2。このため、より"ハードウェアに近い"portをプリミティブにし、futureやspawnはportを使って(静的にコードを生成して)実現することを考える。
R6RSのportと異なり、このportは一方通行になる。

*1:この手法は実はビックリするほどスケールしない。通常の感覚なら、OSが大量のfdをpollするのと、fdの数だけthreadを作ってcondなりなんなりで同期するのは同じ程度の負荷であるべきだろう。しかし、通常のthreadはそういう目的のためには作られていないし、細かな実行時制御もできない。

*2:ふつうはこのようなオーバーヘッドは継続を拡張することで避けられている