hashtablesとserializeライブラリ

久々にyuniに新しいライブラリを足すことにした。どちらも他所でライブラリにしていたけどyuni本体に有った方が何かと便利なんじゃないかということで移動。

hashtables

yuniは基本的に"R7RS smallにあるものはR7RS、そうでなくてR6RSにあるものはR6RS"という方針で選択している。というわけでhashtablesはR6RSベース。
また、R6RSのものを更にサブセットしている:

  • ハッシュ関数は無し。
  • インスペクションは無し。つまり、ハッシュ関数やequality手続きを既存のハッシュテーブルから取り出すことはできない。
  • キーの型限定hashtablesを提供する。integer / string / symbol の3種。

この限定はScheme処理系を既存のインタプリタ言語で実装するケースを想定していて、integerやstringに関しては、それぞれのホスト言語ネイティブのハッシュテーブルを活用できる可能性があることから。
hashtableかhash-tableかは微妙なポイントで、SRFI-125ではhash-tableを推しているが( https://srfi.schemers.org/srfi-125/srfi-125.html )、yuniではプリミティブオブジェクトは1単語原則でhashtableにした。(e.g. byte-vectorでなくbytevector)
実はオブジェクトのハッシュは言語上に言語を載せる場合には良く実装できない可能性があり、可能な限り型付きのハッシュテーブルを想定した方が移植性は高くなる。ハッシュ関数は普通に使えても良いかもしれないが、移植性の問題と、eq/eqvに関しては再現性の問題がある(eq? の成立するオブジェクトに異なるハッシュ値を振る可能性がある)ため実はあんまり役に立たない。R6RSのRationaleでは:

The make-eq-hashtable and make-eqv-hashtable constructors are designed to hide their hash function. This allows implementations to use the machine address of an object as its hash value, rehashing parts of the table as necessary if a garbage collector moves objects to different addresses.

serialize

serializeライブラリは要するにFASLだが、"バイナリ形式のwrite/read"という位置付けとなる。API名は良いものが全く浮ばない。。いっそのこと fasl-read とか fasl-write にしてしまった方が良いかもしれない。。
オブジェクトのシリアライズ機能は処理系によってマチマチで、GambitやChezScheme等のようにちゃんとFASLとして使用できるものもあれば、そもそもシリアライズ機能自体を提供していない処理系もある。
最低でも、全てのread-write invariantなオブジェクトは正常にシリアライズできるという要求で良いと考えているが、問題は"writeできないがシリアライズはできた方が良いもの"をどうするかという問題だろう。
chibi-scheme、chicken、Gauche、Guile、Sagittarius などにはシリアライズの直接的なサポートはない。これらでは自前のWriter/Readerで対応することになる。

... 今見てみるとyuniのターゲット処理系でネイティブのFASLを持つ処理系って3つしか無いのか。。
これらのネイティブサポートは、一般にread/write invariantであるものの他に、ハッシュテーブルやrecordの入出力も可能となっている。ただし、yuniではハッシュテーブルの入出力は上記のinteger/symbol/stringの3種に限定しようとしている。固有のハッシュ関数やequality手続きを持ったハッシュテーブルを入出力するためには、これらの手続きもシリアライズする必要があるが、クロージャシリアライズは処理系のサポートが無いと困難なため。
ネィティブサポートの無い処理系の扱いは悩みどころだが、とりあえずScheme側の実装でどの程度のパフォーマンスになるかを見たいところ。おそらく殆どのケースで、テキスト形式のread/writeには勝てないのではないだろうか。
...じゃぁシリアライズなんて要らないじゃんということになりそうだが、

  1. read/writeだとyuniのプログラムでは頻出するbytevectorのI/Oに使えない
  2. 大きなデータセットを繰り返し処理するようなケースで必要になる - 今のユースケースだとFASL形式で100MiB 〜 500MiB程度のデータを処理しているのでテキスト形式に落してしまうとI/Oオーバヘッドが大きくなってしまう。(Twitterに挙げたgccのログデータセットは、インデックス処理後でも500MiBを超えるくらい https://twitter.com/okuoku/status/1018833935797645312 )
  3. スレッド非サポートの処理系では、並列処理時に結果を受け取る際に使用する。

というあたりで、個人的にはyuniのような位置付けのライブラリにはどうしても必要なんじゃないかと考えている。