オプショナルな多値

プログラミング言語 Scheme では、手続きが複数の値を返すことが出来て、それは多値と呼ばれている。 多値を受取るプリミティブな構文は call-with-values である。 R6RS では let-valueslet*-values もあるし、 R7RS ではそれらに加えて define-values もある。 更に SRFI-8 には receive もある。

しかしこれらの構文は返ってくる値の数が決まっていない場合には使い難い。 ある手続きが返す値が一個だったり二個だったりするような場合が綺麗に表現できない。 そこで、先日紹介した let-optionals* のように、値の数が少ない場合にデフォルト値で埋めるような構文があると便利かもしれないと考えて書いてみた。 下請けに let-optionals* を使うのでそれも含めて R7RS 形式のひとつのライブラリにまとめた。

(define-library (optional)
  (export let-optionals*
          receive-optionals*
          :optional)
  (import (scheme base))
  (begin

    (define-syntax let-optionals*
      (syntax-rules ()
        ((_ args ((var default) . rest) body0 body* ...)
         (let* ((temp args)
                (var (if (null? temp) default (car temp))))
           (let-optionals* (if (null? temp) '() (cdr temp)) rest
             body0 body* ...)))
        ((_ args () body0 body* ...)
         (if (null? args)
             (begin body0 body* ...)
             (error "Too many arguments:" args)))
        ((_ args (var . rest) body0 body* ...)
         (let-optionals* args ((var #f) . rest) body0 body* ...))
        ((_ args rest-var body0 body* ...)
         (let ((rest-var args)) body0 body* ...))))

    (define-syntax :optional (syntax-rules ()))

    (define-syntax %receive-optionals*
      (syntax-rules (:optional)
        ((_ (mandatory-args ...) (:optional . args) expression body0 body* ...)
         (call-with-values
             (lambda () expression)
           (lambda(mandatory-args ... . rest)
             (let-optionals* rest args
               body0 body* ...))))
        ((_ (mandatory-args ...) (head-arg . args) expression body0 body* ...)
         (%receive-optionals*
          (mandatory-args ... head-arg) args expression body0 body* ...))
        ((_ (mandatory-args ...) last-arg expression body0 body* ...)
         (%receive-optionals*
          (mandatory-args ... . last-arg) () expression body0 body* ...))))

    (define-syntax receive-optionals*
      (syntax-rules ()
        ((_ formals expression body0 body* ...)
         (%receive-optionals* () formals expression body0 body* ...))))
    ))

以下のように使える。

(import (optional)
        (scheme base)
        (scheme write))

(display
  (receive-optionals* (a b :optional c (d 1))
      (values 1 2 3)
    (list a b c d)))

こういう場合は手続きの方で戻り値の個数を合わせた方が良い設計に思えるので、あまり出番はないかもしれない。

Document ID: 1337c570221640fead1c0f2c911d5da5