apply の引数

apply: FUNCTION ARG &rest MORE-ARGS

apply は何気に引数を複数に分けて受けられる。でも funcall とは違って、最初見たときは意味がわからん動作をする。
というのは、ARG と MORE-ARGS のうち一番最後の引数は list に突っ込んでおかねばならず、それ以外はそのままでいいというかそのままでないといけない。

#|
;;; 最初は受け取った引数をリストにまとめて返すこんな関数を
;;; 定義して使ってたのだけど、list がまさに同じ動作をする
;;; ことに気づいてしまった
(defun args (&rest args) args)
|#
;;; ふつーに関数 list を呼び出す
(list 1 2 3)
=> (1 2 3)

;;; ふつーに関数 list を apply で呼び出す
(apply #'list '(1 2 3))
=> (1 2 3)

;;; 引数をバラしてみると
(apply #'list 1 2 3)
=> (1 2)  ; 3 はいずこへ

;;; (list への)最初の引数をリストで渡すと
(apply #'list '(1 2) 3)
=> ((1 2))  ; (?_?)

;;; これが正解
(apply #'list 1 2 '(3))
=> (1 2 3)

それで何ができるか

たまに便利なときがある。いくつかの値をリストでまとめて持っていて、それをバラバラの引数として渡したいとき。特に &rest や &key でいくつでも引数を受け取る関数に渡すとき。

;;; &rest で受け取った値をそのまま format に渡す
(defun my-print (fmt &rest args)
  (apply #'format t fmt args))
=> print-to-standard-output

(my-print "~S, ~S, ~S" :foo :bar :baz)
:foo, :bar, :baz
=> nil

あと、xyzzy だとよく見るのが set-text-attribute するとき。

;;; 色とかは設定用の変数で変更できるようにしておいて
(defvar *your-favorit-attribute* '(:foreground 3 :bold t))

;;; 呼び出すときは範囲とタグを指定+変数で apply
(apply #'set-text-attribute
       from to tag
       *your-favorit-attribute*)

ちなみに引数は最低1つ(apply の引数としては2つ)は必要。

(apply #'list)
!! 引数が少なすぎます: (apply #<function: list>)

apply はいったいなにをしているのか?

apply は、第2引数以降に受けた引数をケツから cons していって、最終的にひとつのリストにまとめる(ような動作をする)。

(apply #'list 1 2 '(3))
=> (1 2 3)

(apply #'list (cons 1 (cons 2 '(3))))
=> (1 2 3)

;;; ちなみに、こうなので
(cons 1 (cons 2 '(3)))
=> (1 2 3)

;;; これと同じことになる
(apply #'list '(1 2 3))
=> (1 2 3)

なので、全部バラバラに引数を指定しておいて、更に nil を追加するなんて方法も使える。

(apply #'list 1 2 3 nil)
=> (1 2 3)

(cons 1 (cons 2 (cons 3 nil)))
=> (1 2 3)

というわけで

(defun apply-arg-folding (arg &rest more-args)
  (let ((args (cons arg more-args)))
    (reduce #'cons (butlast args)
            :from-end t
            :initial-value (car (last args)))))
=> apply-arg-folding

(apply-arg-folding 1 2 '(3))
=> (1 2 3)