Scheme:戻り値のスプライシング

Scheme:戻り値のスプライシング

Scheme:多値で出た話題

こうなったら便利じゃないか?

    (+ (values 1 2 3)) => 1 or 6
    (+ (values 1 2 3) (values 4 5 6)) => 5 or 21

から発展した議論。 現在のSchemeの仕様にとらわれず、想像を広げてみるとどうなる?

問題

Shiro: hiraさんが上で期待しているような、applyにおいて多値が引数リストにspliceされる (つまり、(+ (values 1 2 3)) => 6となる) ようなセマンティクスは、 それはそれで有用な場面があります。例えば、関数合成のコンビネータ composeは、多値を考慮すると次のように書かないとなりません。

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

でも、適用時に多値のsplicingが起これば、次のように書くことができます。

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

ただ、これはこれで問題があって、例えば 以下のコードは字面だけ見れば全て同じ動作をするはずですが、 (y)が多値を返した場合に整合性のあるセマンティクスを決めるのは 難しいです。

(x (y))

(let ((a (y))) (x a))

((lambda (a) (x a)) (y))

hira: 常時可変継続の世界において固定長引数に多値を与えた場合、引数の数にマッチしなかったらエラーでいいと思います。 上記のlet/lambdaがお互い別の振る舞いをしたら困るのですが・・・マクロが絡むと別の動作になってしまうのかしら? (x (y)) vs let/lambdaとした場合この二者を同一視できなくなる、というのがじゃじゃ馬たる所以だと思っています。 そうなるともはやSchemeとは言えませんね。

この世界ではcalleeは多値を多用することになりそうです。callerが多値を扱いやすくなっているので、callerの都合を無視できる。そうなると、固定長引数を扱う方がかえって面倒になるので、何でも可変にしてrestでループして、また多値を返すようになるでしょう。こんなconsが多値を返す世界。

(list (cons 1 2 3 4 5 6))
=> ((1 . 2) (3 . 4) (5 . 6))
(list (cons 1 2 3 4 5 6 7))
=> Error

そのうち、この手のループを支援するrest表記がlambdaに導入されたりして。 とにかくまるで別世界・別言語になるんだろうなぁと予想しております。 だからR5RSの多値と継続の仕様はああなったのだろうと思いました。 でもこの世界、一度行ってみたいなぁ。

fuyuki: applyでの多値のsplicingはfoldをいじくってたときにほしくなりました。 なんでfoldは多値を回せないんだろうって疑問に思ったことありません?

Shiro: あるある。無理矢理やろうとすると、srfi-37みたいに 複数のseedを持ち回ることになるんだけれど、あれはあれで使いにくいし、 fold対象のリストが複数になった場合との相性が良くない。結局、 いちいちリストにパックしたりとかしなくちゃならない。

fuyuki: ですよね。どうもSchemeの多値は後付けくさいというか、いまいち馴染みきっていない感じがします。

fuyuki: あと、私は多値を "performance hack" とする考えには無理があるのではないかと思っています。なんでかというと、多値を考慮するとどうしてもcons/applyが増えるから。上のcomposeの例もそうですが、begin0なんか多値有りと無しとでコストがかなり違うはずです。

teranishi: この世界では、letを単純にlambdaで定義するわけにはいかないようです。

gosh> (define-syntax let
        (syntax-rules ()
          ((_ ((name val) ...) body ...)
           ((lambda (name ...) body ...) val ...))))
#<syntax let>
gosh> (let ((a (values)) (b (values 1 2))) (list a b))
(1 2)

お試し実装

teranishi:

見た目だけですが。

(define-module splicing
  (use srfi-1)
  (export convert-values-receive convert-values-send
          convert-values-multiply new-lambda new-eval)
  (define packed-values-tag (cons () ()))
  (define make-packed-values (pa$ cons packed-values-tag))
  (define (packed-values? val)
    (and (pair? val) (eq? (car val) packed-values-tag)))
  (define packed-values-list cdr)
  (define (convert-values-receive proc)
    (lambda args
      (apply proc (append-map (lambda (x)
                                (if (packed-values? x)
                                  (packed-values-list x)
                                  (list x)))
                              args))))
  (define (convert-values-send proc)
    (convert-values-receive
      (lambda args
        (receive ret (apply proc args)
          (if (and (pair? ret) (null? (cdr ret)))
            (car ret)
            (make-packed-values ret))))))
  (define (convert-values-multiply proc)
    (let ((len (arity proc)))
      (convert-values-send
        (lambda args
          (let loop ((args args) (result ()))
            (if (null? args)
              (apply values (reverse result))
              (receive (arg rest)
                (split-at args len)
                (loop rest (cons (apply proc arg) result)))))))))
  (define-syntax new-lambda
    (syntax-rules ()
      ((_ args body ...)
       (convert-values-receive (lambda args body ...)))))
  (define (new-eval expr env)
    (let ((ret (eval expr env)))
      (if (packed-values? ret)
        (apply values (packed-values-list ret))
        ret)))
  )
(import splicing)
(define lambda new-lambda)
(define values (convert-values-send values))
(define cons (convert-values-multiply cons))
(define list (convert-values-receive list))
(define +    (convert-values-receive +))
(define (main args)
  (read-eval-print-loop #f new-eval))

多値を渡す側も受ける側も手を加えなければならないのが難ですが。

Tag: 多値

More ...