読者です 読者をやめる 読者になる 読者になる

オプショナル引数

先日は Scheme でオプショナル引数を扱うマクロを書いた。
http://saito.hatenablog.jp/entry/20110310/1299765814
だが、手続きが呼び出される度に引数の検査をしなければならない。
今度は手続きを呼び出す側でオプショナル引数のデフォルト値を補充するような仕組みを考えた。
言葉で説明するのは面倒なので例となるコードで表してみよう。

(define-optionals (sample a :optional b (c 'hoge) . r)
    (list a b c r))

これを展開した結果として

(begin
   (define (t a b c . r) (list a b c r))
   (define-syntax sample
     (syntax-rules ()
       ((_ a) (t a #f 'hoge))
       ((_ a b) (t a b 'hoge))
       ((_ a b c . r) (t a b c . r)))))

が生成されるようにすれば、

(sample 1)

という式は実際には

(t 1 #f 'hoge)

となるので、実行時の検査が不要になり、ちょっと効率的だ。
もちろん、ここで定義した sample は実際には手続ではなくマクロなので、高階手続きに渡せない等の制限はどうしてもできてしまう。 汎用的なユーティリティとしてよりは、パフォーマンスチューニングに使う類のテクニックかもしれない。
そして実装はこちら。 可変長引数を考慮してたりだとか、デフォルト値を省略した場合には #f になるだとかいった点でも一応 Gauche の define に似せてある。 R6RS の範囲内で実装できているつもり。

(define-syntax def%%%
  (syntax-rules ()
    ((_ name t (args ...)
        (clause ...) (opt-args ...) rest () body ...)
     (begin
       (define (t args ... opt-args ... . rest) body ...)
       (define-syntax name
         (syntax-rules ()
           clause ...
           ((_ args ... opt-args ... . rest)
            (t args ... opt-args ... . rest))))))
    ((_ name t (args ...)
        (clause ...) (opt-args ...)
        (a . as) (default-val default-vals ...)
        body ...)
     (def%%% name t (args ...)
       (clause ...
               ((_ args ... opt-args ...)
                (t args ... opt-args ... default-val default-vals ...)))
       (opt-args ... a) as (default-vals ...)
       body ...))))

(define-syntax def%%
  (syntax-rules ()
    ((_ name t (args ...)
        (opt-args ...) (default-vals ...) ((opt-arg default-val) . rest)
        body ...)
     (def%% name t (args ...)
       (opt-args ... opt-arg) (default-vals ... default-val) rest
       body ...))
    ((_ name t (args ...)
        (opt-args ...) (default-vals ...) (opt-arg . rest)
        body ...)
     (def%% name t (args ...)
       (opt-args ... opt-arg) (default-vals ... #f) rest
       body ...))
    ((_ name t (args ...)
        (opt-args ...) default-vals rest
        body ...)
     (def%%% name t (args ...) () () (opt-args ... . rest) default-vals
       body ...))))

(define-syntax def%
  (syntax-rules (:optional)
    ((_ name (args ...) (:optional . options) body ...)
     (def%% name t (args ...) () ()  options body ...))
    ((_ name (args ...) (arg . rest) body ...)
     (def% name (args ... arg) rest body ...))
    ((_ name (args ...) rest body ...)
     (def%% name t (args ...)  ()  () rest body ...)
     )))

(define-syntax define-optionals
  (syntax-rules ()
    ((_ (name . args) body1 body2 ...)
     (def% name () args body1 body2 ...))))

さて、最初に展開形を示したときは手続きの名前を便宜上 t で表したが、実際には syntax-rules が衝突を回避するので何者とも衝突しないような名前になっているはずだ。 つまり、同時に定義したマクロを通してしかアクセスできない手続きであるということになる。 このように直接にはアクセス不能な手続とインターフェイスとしてのマクロという組合せは以前にも使っている。
http://saito.hatenablog.jp/entry/20100822/1282449327
http://saito.hatenablog.jp/entry/20100830/1283099968
なかなか使い勝手の良いテクニックだと思う。
Document ID: cd59721c87aaa0506c479c1027a3e1fa