#:g1: multiple-value-variantsの紹介

Posted 2014-02-02 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の34日目です。

multiple-value-variantsとはなにか

 multiple-value-variantsは、多値を返す場所を拡張するようなユーティリティです。

パッケージ情報

パッケージ名multiple-value-variants
Quicklisp
参考サイトmultiple-value-variants | Libraries | HexstreamSoft
CLiKihttp://cliki.net/multiple-value-variants
Quickdocshttp://quickdocs.org/multiple-value-variants

インストール方法

(ql:quickload :multiple-value-variants)

試してみる

 どんな関数があるかは、Quickdocsで確認できます。

 基本的な説明は上記の作者サイトに書いてありますが、基本的には多値を返してくれたら良いのになあというところで多値を返すようにしてみましたというものです。
mapcarでの例がわかりやすいかなと思いますが、

(mv-variant:multiple-value (2)
  (mapcar #'truncate '(3 5/4 5.5)))
;=>  (3 1 5)
;    (0 1/4 0.5)

という感じでmultiple-valueで囲んだフォームが、多値を返すならこんな感じかなというような動作をします。
とはいえ、上記の例でいえば、mapcarになにか細工をしている訳ではなく、実際にはmultiple-valueがmapcarの式を引数として取って変形しています。

 上記のmapcarは、

(LET ((FUNCTION2837 #'TRUNCATE)
      ACCUMULATE1-2840
      ACCUMULATE2-2841
      FINISH1-2842
      FINISH2-2843)
  (PROGN
    (LET* ()
      (MULTIPLE-VALUE-BIND (NEW2844 NEW2845)
                           (MV-VARIANT::%MAKE-LIST-ACCUMULATOR)
        (VALUES (SETQ ACCUMULATE1-2840 NEW2844) (SETQ FINISH1-2842 NEW2845))))
    (LET* ()
      (MULTIPLE-VALUE-BIND (NEW2846 NEW2847)
                           (MV-VARIANT::%MAKE-LIST-ACCUMULATOR)
        (VALUES (SETQ ACCUMULATE2-2841 NEW2846) (SETQ FINISH2-2843 NEW2847)))))
  (MAPC
   (LAMBDA (LIST1-2836)
     (MULTIPLE-VALUE-BIND (VALUE1-2838 VALUE2-2839)
                          (FUNCALL FUNCTION2837 LIST1-2836)
       (FUNCALL ACCUMULATE1-2840 VALUE1-2838)
       (FUNCALL ACCUMULATE2-2841 VALUE2-2839)))
   '(3 5/4 5.5))
  (VALUES (FUNCALL FINISH1-2842) (FUNCALL FINISH2-2843)))
;=>  (3 1 5)
;    (0 1/4 0.5)

のように展開されますが、mapcarとは全然別物になっていることが分かると思います。
Seriesなどでも使われているテクニックですが、上層の括弧を押えてしまえばなんでもありなのか、と良くも悪くも考えさせられるテクニックです。

 以上の説明のような仕組みなので、multiple-valueで囲めば任意の多値を返す式が得られるという訳ではなく、自分で変形を定義してやることになります。
例としてデバッグ時に便利そうな、printの多値バージョンを作成してみます。

(mv-variant:define print () (form &optional (stream t))
  (let ((vs (gensym "vs-"))
        (x (gensym "x-")))
    `(let ((,vs (multiple-value-list ,form)))
       (declare (dynamic-extent ,vs))
       (mapc (lambda (,x) (print ,x ,stream)) ,vs)
       (values-list ,vs))))

(mv-variant:multiple-value () (print (floor 3 4))) ;>> 0 ;>> 3 ;=> 0 ; 3

 流れにあまり関係ないですが、なんとなく、リーダーマクロと組み合わせるとすっきりする気がしたので、リーダーマクロを設定してみます。

(named-readtables:defreadtable :mv
  (:merge :standard)
  (:dispatch-macro-char #\# #\v
                        (lambda (s c a)
                          (declare (ignore c))
                          `(mv-variant:multiple-value ,(and a (list a))
                             ,(read s t nil t)))))

(named-readtables:in-readtable :mv)

#2v(mapcar #'truncate '(3 5/4 5.5)) ;=> (3 1 5) ; (0 1/4 0.5)

#2v(mapcar #'truncate '(3 5/4 5.5)) ;=> (3 1 5) ; (0 1/4 0.5)

すっきりはしますが、謎も増えました。

まとめ

 今回は、multiple-value-variantsを紹介してみました。
multiple-valueのような式の乗っ取りマクロもなかなか面白いので、興味のある方は探求してみてはどうでしょうか。

 ちなみにエントリーに関係ない雑学ですが、Lisp machine Lispでmultiple-value-setqが登場した当初は、今から考えると不思議ですが、multiple-valueという名前でした。
さすがに分かり難いのでCommon Lispではmultiple-value-setqという名前になり現在に至りますが、Lispマシンのソースを読む場合などに思い出してみて下さい。

comments powered by Disqus