複数の値を返す手続き


Tags: R6RS, 手続き, マクロ

Scheme では手続きは values 手続きを使って任意個数の値を返すことができる。標準の手続きでは例えば、 div-and-mod などが複数の値を返す。

ところで、 hashtable-ref 手続きは default 値にハッシュテーブル内に存在しうる値を指定すると、それがハッシュテーブル内に存在した値なのか default 値に渡した値なのか区別できない。そこで、例としてハッシュテーブルの参照を行ない、 2 値目にキーに対応した値がハッシュテーブルに存在したかどうかを返す手続き hashtable-ref* を考える。

(import (rnrs))

(define (hashtable-ref* ht key default)
  (values (hashtable-ref ht key default)
          (hashtable-contains? ht key)))

(define ht (make-eqv-hashtable))

(hashtable-set! ht 1 'one)
(hashtable-set! ht 3 'three)

(hashtable-ref* ht 1 'one) ; => one #t
(hashtable-ref* ht 2 'two) ; => two #f
(hashtable-ref* ht 3 'three) ; => three #t

このように手続きは任意個の値を返す可能性があるので、常にそのことを考慮しておかなければならない。関数の合成行なう手続き compose を定義する場合、次のような定義では g が多値を返した場合に動作が規定されない。

(import (rnrs))

(define (compose f g)
  (lambda args
    (f (apply g args))))

多値を考慮する場合次のようにする。

(import (rnrs))

(define (compose f g)
  (lambda args
    (call-with-values (lambda () (apply g args)) f)))

マクロの場合も同じである。例えば begin のように式を順に評価し、最後の値ではなく最初の値を返すような構文 begin0 を定義したいと考える。このとき、次のような定義では最初の式が複数の値を返した場合の動作が規定されない。

(import (rnrs))

(define-syntax begin0
  (syntax-rules ()
    ((_ e es ...)
     (let ((v e))
       es ...
       v))))

次のように定義するとうまく行く。

(import (rnrs))

(define-syntax begin0
  (syntax-rules ()
    ((_ e es ...)
     (let-values ((v e))
       es ...
       (apply values v)))))