APPLY — 인자 리스트로 함수 호출
APPLY — 인자 리스트로 함수 호출
APPLY는 함수예요. 함수를 호출하는데, 인자들을 하나하나 나열하는 대신 리스트로 묶어서 넘겨줄 때 써요. 함수를 가리키는 객체(디자이네이터)와 인자 리스트만 있으면, 그 함수를 그 리스트의 원소들을 인자로 해서 호출해 주죠.
본문
시그니처는 이렇게 생겼어요.
apply function &rest args+ => result*
- function — 함수 디자이네이터(function designator).
- args — 확장 가능한 인자 리스트 디자이네이터(spreadable argument list designator). 마지막 인자가 "펼쳐질(function의 마지막 인자들로)" 리스트예요.
- results —
function이 돌려준 값들.
동작은 이렇습니다. apply는 function을 args에 대해 호출해요. 마지막 인자가 리스트라서, 그 리스트의 각 원소가 function의 개별 인자로 펼쳐져요.
주의할 점이 있어요. 함수가 &rest로 인자를 받을 때, 구현체가 그 rest 파라미터를 apply의 마지막 인자와 구조를 공유하는 객체에 바인딩하는 것이 허용되지만 필수는 아니에요. 그리고 함수는 "자신이 apply로 호출됐는지", 또 "마지막 인자가 상수인지"를 감지할 수 없어요. 그래서 표준에 부합하는 프로그램은 rest 리스트의 리스트 구조가 새로 cons됐다고 믿어서도, 그 리스트 구조를 수정해서도 안 돼요.
setf는 특정 상황에서 apply와 함께 쓸 수 있어요(Section 5.1.2.5, APPLY Forms as Places 참고).
예제를 볼게요.
(setq f '+) => +
(apply f '(1 2)) => 3
(setq f #'-) => #<FUNCTION ->
(apply f '(1 2)) => -1
(apply #'max 3 5 '(2 7 3)) => 7
(apply 'cons '((+ 2 3) 4)) => ((+ 2 3) . 4)
(apply #'+ '()) => 0
apply는 함수 객체뿐 아니라 심볼('cons) 같은 디자이네이터도 받아요. 또 앞부분 인자는 그냥 값으로, 마지막 인자만 리스트로 주면 그 리스트가 펼쳐져요. (apply #'max 3 5 '(2 7 3))은 (max 3 5 2 7 3)을 호출한 셈이라 7이 나오죠.
&rest 공유와 관련된 예제도 살펴볼게요.
(defparameter *some-list* '(a b c))
(defun strange-test (&rest x) (eq x *some-list*))
(apply #'strange-test *some-list*) => implementation-dependent
strange-test는 &rest로 받은 리스트가 *some-list*와 같은 객체인지 보는데, 결과가 구현 의존적(implementation-dependent)이에요. rest 리스트가 새로 만들어질 수도 있고 원 객체를 공유할 수도 있기 때문이죠.
rest 리스트를 수정하는 건 애초에 허용되지 않는다는 점을 보여주는 예제예요.
(defun bad-boy (&rest x) (rplacd x 'y))
(bad-boy 'a 'b 'c) has undefined consequences.
(apply #'bad-boy *some-list*) has undefined consequences.
rplacd로 rest 리스트 구조를 바꾸면 결과가 정의되지 않아요(undefined consequences).
마지막으로, apply가 인자를 유연하게 전달하는 유용한 사례를 볼게요. make-array에 키워드 인자 리스트를 그대로 전달하는 함수예요.
(defun foo (size &rest keys &key double &allow-other-keys)
(let ((v (apply #'make-array size :allow-other-keys t keys)))
(if double (concatenate (type-of v) v v) v)))
(foo 4 :initial-contents '(a b c d) :double t) => #(A B C D A B C D)
keys로 모은 키워드 인자들을 apply로 펼쳐 make-array에 넘기고, :double t면 배열을 두 배로 이어 붙이는 코드예요.
관련 함수로는 funcall, fdefinition, function이 있고, Section 3.1(Evaluation)과 Section 5.1.2.5를 함께 보면 좋아요.