Conditional Expression パターンで複数の値を返したい — #:g1

Posted 2011-02-12 06:22:00 GMT

Conditional Expressionパターンとは、

(format t
        "~A~%"
        (if (zerop (random 2)) "foo" "bar"))
;-> bar
;
;=> NIL
;のようなもので、値を返すのが基本の言語を使っている人なら割と慣れ親しんだ記法かと思います。
ifが式でなくて、値を返さない言語だと
(if (zerop (random 2))
    (format t "~A~%" "foo")
    (format t "~A~%" "bar"))
;-> foo
;
;=> NIL
のように書くことになりますが、この辺りの扱いの違いに目をつけてパターンとして名前を付けたのかもしれません。
結構好きなパターンですが、Smalltalkベストプラクティスを読んだときに名前が付いているのをみつけました。

それはさておき、このConditional Expressionパターンは、1つの値を返すときは、上の例のように書けば良いのですが、複数の値を返したい場合や、部分的に共通のところがあったりした場合に、どうしたもんかと考えています。
リストを返してapplyというのも良いと思いますが、無駄なリストが作られるので、ここは多値だろう、ということで
(multiple-value-call #'format t "~A: ~A ~A~%"
  (if (zerop (random 2))
      (values "foo" 2 "foo")
      (values "bar" 2 "baz")))
;-> foo: 2 foo
;
;=> NIL
と書いてみます。
しかし、
(if (zerop (random 2))
    (format t "~A: ~A ~A~%" "foo" 2 "foo")
    (format t "~A: ~A ~A~%" "bar" 2 "baz"))
;-> foo: 2 foo
;
;=> NIL
と比べるとなんだか難解。過ぎたるは及ばざるが如しか…。

ちなみに、SBCLの場合ですが、多値で返して、MULTIPLE-VALUE-CALLするのも、リストを返してAPPLYするのも効率的には同じようです。
(dotimes (i 100000)
  (multiple-value-call #'values 8 (if t (values 8 8) (values 9 9))))
;⇒ NIL
----------
Evaluation took:
  0.008 seconds of real time
  0.010000 seconds of total run time (0.010000 user, 0.000000 system)
  125.00% CPU
  20,665,188 processor cycles
  0 bytes consed

Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz

(dotimes (i 100000)
  (apply #'values 8 (if t (list 8 8) (list 9 9))))
;⇒ NIL
----------
Evaluation took:
  0.009 seconds of real time
  0.010000 seconds of total run time (0.010000 user, 0.000000 system)
  111.11% CPU
  21,332,376 processor cycles
  0 bytes consed ; コンスされていない…

Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz

理由はAPPLYがMULTIPLE-VALUE-CALLの式に変形されるという最適化が施されているからのようです。
;;;; transforming APPLY

;;; We convert APPLY into MULTIPLE-VALUE-CALL so that the compiler ;;; only needs to understand one kind of variable-argument call. It is ;;; more efficient to convert APPLY to MV-CALL than MV-CALL to APPLY. (define-source-transform apply (fun arg &rest more-args) (let ((args (cons arg more-args))) `(multiple-value-call ,fun ,@(mapcar (lambda (x) `(values ,x)) (butlast args)) (values-list ,(car (last args))))))


若干悔しいので MULTIPLE-VALUE-CALLの方が便利な局面を探してみました。
一つ見付けたのは、 MULTIPLE-VALUE-CALL だと、最後の要素が複数でも単数でも良いので、
(multiple-value-call #'values 8 
                              (if t (values 8 8) (values 9 9)) 
                              (values 'x 'y) 
                              'z)
;=> 8
;   8
;   8
;   X
;   Y
;   Z
みたいなことができます。

なんやかんやと書きましたが、とりあえず処理系全般的にリストを返すよりは効率が良い筈ではあります。

comments powered by Disqus