多値アクセス構文

ML(やHaskell)のプログラマがタプルを使うような文脈でSchemerは多値を使うと思うんですが、値が一つだけ欲しいという場合に多値はちょっと不便なんですよね。

MLだとfstやsndというアクセサが使えるんですが、それに相当するものが無いなと思って、作ってみました。

(define (pos x)
  (or (string->number
       (substring (symbol->string (syntax-e x)) 1))
      1))

(define (positional-args var)
  (let loop ((n 1)
             (l (list (cons (pos var) var)))
             (r '()))
    (cond ((null? l)
           (let ((vars (reverse (map cdr r))))
             (append vars (gensym))))
          ((= (caar l) n)
           (loop (add1 n)
                 (cdr l)
                 (cons (car l) r)))
          (else
           (loop n
                 (cons (cons n (gensym)) l)
                 r)))))

(define-syntax (def-valref stx)
  (syntax-case stx ()
    ((def-valref ref _n)
     (with-syntax ((args (positional-args #'_n)))
       (syntax/loc stx
         (begin
           (define ref~ (lambda args _n))
           (define-syntax ref
             (syntax-id-rules ()
               ((ref v)
                (call-with-values (lambda () v) ref~))
               (ref ref~)))
           (provide ref)))))))

(def-valref fst _1)
(def-valref snd _2)

positional-argsは、例えば _3 というシンボルを与えると (g97 g98 _3 . g99) というリストを返す関数です。これをそのままラムダのパラメータ位置に置けば、目的の位置引数に変数をバインドできるわけです。


利用例です。このように、関数を置けない所で関数呼び出しっぽいスタイルで値を取り出すことができます。

> (fst (values 1 2 3))
1

こういう使い方も可能です。

> (call-with-values (lambda () (values 1 2 3))
    fst)
1

[追記]

後から気づきましたが、PLT Scheme の compose は多値を返す関数にも対応しているので、

(map (compose fst multi-valued-func) lst)

という使い方もできますね。

なお、fst はあくまでもマクロであることを強調しておきます。