制約付き手続きの実装 - 実装方法の検討


(簡単のため、図中のsendは手続きのように描かれているが、実際にはマクロとなる。これは _ (underscore)をパラメタの省略記号として使用するためで、_は補助構文なのでマクロの外では使用できない。)
yuniFFIの重要なポイントは、構造体や手続きに"制約"(constraint)を設定することで、必要なパラメタの一部を省略することができるという点と言える。これを実装するための構文 define/constraint をどのようにすれば実装できるかを考える。
図中に登場する構文のうちconstraintを設定するために必要なものは:

  • (define-constraint SPEC-NAME CONSTRAINT-CLAUSE ...)

define-constraintは、constraint spec objectを定義する。定義されたconstraint spec objectはCPSマクロとなり、後段のconstraint-apply構文で内容をクエリされる。
次のdefine/constraintと名前がまぎらわしいので、define-constraint-specくらいの名前にした方が良いかもしれない。
図中の"bytelength"や"constant"は制約オペレータと呼び、実際の値の補間を行う。

  • (define/constraint PROC-NAME SPEC-NAME (BASE-PROC SPEC-PARAM ...))

define/constraintは、BASE-PROCに対してSPEC-NAMEで指定したconstraint specの制約を適用した手続きを起動するマクロPROC-NAMEを定義する。
図中では、このdefine/constraintの展開過程を書いている。
そのままだと非常に使いづらいので、

(define/constraint (send target)
  $fix01
  (%send-base target target-size constant-one))

のようにして直接手続きを生成できるようにした方が良いかもしれない。また、実際には殆どのケースでBASE-PROCとSPEC-NAMEは1対1対応するので、define-constraintの段階でBASE-PROCを指定させる方が好ましい。

キーワードの一致をどうやってマクロに記述するのか問題

問題は、yuniFFIは可能な限りR7RS Schemeの範囲で記述しなければならないので、R7RSの(というかR5RS schemeの伝統的な)syntax-rulesを使用して実装する必要がある点。
しかし、syntax-rulesは健全なマクロであるため、"字面上の"シンボルの一致をチェックする方法が存在しない
字面上のシンボルの一致とは、例えば、図中で出現する2つのtarget同士、つまり、1) define-constraint構文中で参照されているtarget と 2) define/constraint構文中に書かれ、constraint-apply 構文の中に展開されるtarget が一致することをチェックする必要がある。図では同じソース中に記述されているため見れば判りそうだが、実際のユースケースでは、define-constraintで定義した制約のうちエクスポートされるのは$fix01のみでtarget等の指定子はエクスポートされない。このため、syntax-rules的にはこれらのtargetは別物と見做される。
これに対するワークアラウンドはいくつか考えられるが、いづれも低レベルマクロプリミティブを用意しないと実際の実装では厳しい。また、どの方法もマクロの健全性を損なう。

  • (中間オブジェクトを使用する方法)

パフォーマンスを気にする必要が無ければ、マクロによる実装を諦め、一旦hashtable等をランタイムに生成して解決するように実装する方がずっと容易と言える。ただし、yuniFFIの場合、補間コードはFFI関数の呼び出し毎に実行される可能性があるため、可能なかぎり中間オブジェクトの生成を避ける必要がある。

  • 文字列に変換する方法

シンボルによるマッチを使用せず、文字列にする。R5RS syntax-rulesは文字列をテンプレートに指定できるため、これはそれなりに使用されている(例えば http://d.hatena.ne.jp/mjt/20130708/p1 )。
syntax-rulesのみを使用してシンボルから文字列への変換を実装することは不可能なので、変換のために低レベルマクロプリミティブを実装毎に用意する必要がある。
文字列をリストにwrapする方法を使うと多少安全にできる。

;; 通常の手続きだと思って1とか2を直接渡すと危険(= エラーにならないかもしれない)
(%theMacro0  "1"   "2")

;; ("1")や("2")はマクロの外で評価すると常にエラーとなるため、
;; 比較的安全にキーワードとして使用できる 
(%theMacro1 ("1") ("2")) 

このテクニックはR7RSのparameterizeサンプルでも使用している。

  • 数値(正確数)に変換する方法

文字列の代りに正確数を使用しても同じことができる。文字列を使用した場合に比べ、マクロはより脆弱になる(間違えて普通に数値を与えた場合に誤動作する)し読み辛いコードになる。そのかわり、文字列の比較よりも数値の比較の方が高速なので多少の高速化が期待できる。

(実装編に続く)

というわけで、

  1. 低レベルマクロプリミティブを追加して上記のような変換を実装するか、
  2. 構文を調整してその必要性を無くす

のどちらかの対応が必要と言える。まだ考え中。