record-typeとはなんなのか

最近のmoshに対する変更に追従する過程で問題になったのはR6RS record-typeの取り扱い。
R6RSのrecord、平たく言えば構造体には(rnrs records procedural)と(rnrs records syntactic)の2通りのライブラリが用意されている。proceduralは、スクリプトの実行中(runフェーズ)にrecordを構築するためのものであり、syntacticはスクリプトの展開時(expandフェーズ)にrecord型を構築するためのものとなっている。
syntacticに作ったrecord typeは、その物理的表現がexpandの段階で決定でき、必要に応じてvectorに展開するといった操作が(原理的には)事前に行える。
...が、nmoshはこれらの最適化を諦めて、proceduralなrecordとsyntacticなrecord typeを区別せずに取り扱っている。よって、record typeを実行時に変更することが原理的にはできる。(ただし、moshR6RSに準拠するために、そのようなインターフェースを提供していない。)
recordがこんなに熱心に規定されているのには、R6RSが例外処理にrecordを使うことが背景に有るとも言える。R6RSの例外は、recordを使って実装しており、継承の機能を使って例外を階層化している。
もし、recordを適切に最適化できるコンパイラが有るなら、例外処理も同様に最適化できる可能性がある。。

複雑すぎる

R6RS recordの仕様は混乱しているように思える。(もっとも、yuniはR6RS recordに物理表現レイヤを付け加えるかたちで設計されているため、これらとは密接な関係にある)
3つの異なる概念を理解する必要がある。

  • record type - syntacticレイヤのdefine-record-typeで定義することのできるrecordの型。実行時には変更できない。
  • record type descriptor(RTD) - proceduralレイヤで生成できるrecordの型。
  • constructor descriptor(CD) - あるRTDに関連付けられた、構築手続きを生成するためのオブジェクト。

全く最適化を考慮しないなら、define-record-typeは単に(define name (cons RTD CD))のようなもので代替できる。nmoshのrecord実装はLarcenyのSRFI-99による実装を移植して使っており、実際にこのような実装になっている。よって、スクリプトの実行中にset!なりなんなりでrecord typeの定義を変更することができてしまう。
(ちなみに、例外の型は変更できない。これは単に"外部のライブラリに由来するシンボルはmutateできない"という別のルールによる。)
RTDは、型をsealする等、一般的なオブジェクトシステムに要求される仕様を備えている。言語全体をオブジェクト指向な言語にするのではなく、recordにそれを任せてしまっているがために複雑化しているように思える。
RCDに関しては、SRFI-99で単に不要とされてしまった。

Neither the (ratified) R6RS library document nor the (unratified) R6RS rationale consider the fact that the constructor-descriptor mechanism adds unnecessary complexity to what is by far the most common case: record definitions that do not require specialized constructors.

教訓

R6RS recordsのような仕様を生み出さないために :

  • 明確で一般的なユースケースを想定する
  • 最適化に関する部分を共通化しようとしない(どうせ他の部分でよりエレガントな手法が必要になる)
  • シンプルでない解決策は避ける

yuniもこの教訓を元に機能を追加していきたいところ。