inkar-us-i.hatenablog.com

この記事で少しだけ多値について紹介しましたが、この頃はまだよく存在意義がわかっていませんでした。

そもそも多値とは?

大雑把に説明すると、valuesとかhoge&fuga関数を呼んだ時に値が複数返ってくるアレです。

(values 3 2 1)
;; => 3 2 1 (※注: リストではなく、あくまで値が複数ある)
(min&max 1 9 3 2 5 6)
;; => 1 9

多値を受け取るには、receive, call-with-values, let-values等を使います。

(call-with-values (^() (min&max 1 3 5))
  (^(min max)
    (format #f "min: ~s, max: ~s" min max)))
;; => "min: 1, max: 5"

(use srfi-8)
(receive (a b c) (values 3 3 4)
  (list a b c))
;; => (3 3 4)

(use srfi-11)
(let-values (((quot rem) (quotient&remainder 5 2)))
  (format #f "5 ÷ 2 = ~sあまり~s" quot rem))
;; => "5 ÷ 2 = 2あまり1"


Schemeでは、多値はファーストクラスの概念ではありません。すなわち、「多値」そのものを値として扱うことはできないということです。

gosh> (define vals (values 3 2 1))
vals
gosh> vals
3

このように、valsにはvaluesの最初の値しか代入されていません。

このことからわかるように、Schemeでは、「多値」を持つ変数を作ることはできません。しかし、その一方で「多値を返す」関数を作ることはできます。

これがどういうことか、それを理解するためには、まずはSchemeにおける関数について理解しなければなりません。

Schemeにおける関数とreturn

Schemeは継続がファーストクラスとなっている、非常に特殊な言語です。
そのため、見かけ上通常の「関数」のように見える以下のコードも、

(define (add x y) (+ x y))
(print (add 3 2))
...

実質的には以下のコードと同じようなものになります。

(define (add/cps x y cont) (cont (+ x y)))
(add/cps 3 2 (^(sum)
               (print/cps sum (^(_)
                                ...))))

(ここで、hoge/cpsは元の関数hogeを継続渡し形式にしたものです。)

上のコードのaddと、add/cpsの定義の違いについて注意してみましょう。

addの本体は

(+ x y)

として呼び出し元へ値を「返して」いるかのように見えますが、その一方でadd/cps

(cont (+ x y))

というように、戻り値を「返して」いるのではなく、戻り値に相当する値を引数として、現在の継続を呼び出しています。
すなわち、Schemeには「関数呼び出し」と「関数からのreturn」の間には違いがないのです。

多値を「返す」こと

Schemeにおけるreturnが手続きの呼び出しに他ならないのであれば、手続きに複数の引数を与えるのと同様に、複数の値に対するreturnができるはずです。
実際に、以下のような現在の継続に複数の引数を与えるコードは以下のようにして書くことができます。

(call/cc (^(cont) (cont 1 2 3)))

実は、これが多値の正体です。R7RSではvaluesは以下のように定義されています。

(define (values . things)
  (call-with-current-continuation
   (lambda (cont) (apply cont things))))

そして、receiveなどは現在の継続が複数の引数を持てるようにする構文なのです。

実際に、同じように定義した自作valuesを使っても、receive等の多値を扱う構文は正常に機能します。

(define (my-values . args)
  (call/cc (^(cont) (apply cont args))))
(receive (a b c) (my-values 1 2 3)
  (+ a b c))
;; => 6

たしかにreturnと手続きの呼び出しが等価ならば、現在の継続に複数の値を渡すことによって複数の値をreturnできるはずです。
しかし、だからと言って実際に複数の値を(リストやペアなどにせずに)返せてしまうのは面白いですね。
こんなことができる言語はSchemeくらいでしょう。(というか、そもそもファーストクラスの継続が扱える言語がSchemeくらいなので)