수정하기 힘든 패키지에 있는 함수를 수정할 때 사용하는 advice

2 minute read

The advice feature lets you add to the existing definition of a function, by advising the function. This is a cleaner method than redefining the whole function.

Advising Functions (GNU Emacs Lisp Reference Manual) - gnu.org

기존 함수 호출 전, 호출 후, 혹은 기존 함수 호출까지 내가 책임질 수 있다. 조언(advice)이라니 재미있는 표현이다.

필요할 때

내가 컨트롤하지 못하는 함수를 수정해야 할 때, 유용하게 사용할 수 있다. 난 사용 중인 패키지의 특정 코드나 emacs 기본 함수 동작을 변경할 때, 주로 사용한다. 혹은 특정 함수가 호출된 후에 호출하는 hook이 없어서 advice를 사용한다. ’TODO 상태와 org-clock은 같이 움직인다’ 글에서 org-clock-in 함수가 호출됐을 때, 후속 작업으로 org todo 상태를 바꾸고 싶어서 advice를 사용했다.

기본적인 사용법

(defun awesome-func (&rest args)
  (apply '+ args))

(awesome-func 1 2 3)
6

인자를 더하는 개쩌는 함수가 있다.

(defun advice-awesome-func (&rest args)
  (message "called advice-awesome-func")
  (* (apply args) 2))

(advice-add #'awesome-func :around #'advice-awesome-func)

:aroundadvice-awesome-func 조언 함수(advising function)를 추가한다. awesome-func 함수 호출 전에 메시지를 출력하고 결괏값에 2를 곱한다.

(awesome-func 1 2 3)
12

*Messages* 버퍼에 called advice-awesome-func 문자열을 출력하고 리턴값은 6이 아니라 12가 된다. awesome-func 는 손도 안 대고 동작을 변경했다. 조언(advice)은 이런 건가 보다. 실제 대상을 변경시키지는 못한다.

(advice-remove #'awesome-func #'advice-awesome-func)

advice-remove 로 조언 함수를 제거할 수 있다.

(awesome-func 1 2 3)
6

이제 원래 값이 나온다.

:around 이외의 방법

원래 함수 호출까지 책임지는 :around 가 가장 강력하다. 이걸로 다 문지르려고 하지 말고 방법이 여러 개 있으니 더 명확한 걸 사용하자. 코드를 읽는 데 도움이 된다.

:before

(defun advice-awesome-func-before (oldfunc &rest args)
  (message "args: %S" args))

(advice-add #'awesome-func :before'advice-awesome-func-before)
(awesome-func 1 2 3)
6

함수 호출되기 전에 뭔가를 하고 싶으면 사용한다.

:after

(defun advice-awesome-func-after (oldfunc &rest args)
  (apply '- args))

(advice-add #'awesome-func :after #'advice-awesome-func-after)
(awesome-func 1 2 3)
6

함수가 호출된 후에 뭔가를 하고 싶을 때, 사용한다. 리턴 값을 대체할 순 없다.

:filter-args

(defun advice-awesome-func-filter-args (args)
  (mapcar (lambda(x) (+ x 1)) args))

(advice-add #'awesome-func :filter-args #'advice-awesome-func-filter-args)
(awesome-func 1 2 3)
9

:filter-args 키워드를 사용하면 인자가 리스트로 넘어온다. 조언 함수에서 인자를 rest& args 로 받으면 안 되고 args 로 받아야 리스트를 받는다. 그걸 처리하고 리스트로 리턴하면 이 값이 awesome-func 인자로 들어간다.

:filter-return

(defun advice-awesome-func-filter-return (args)
  (format "return value: %S" args))

(advice-add #'awesome-func :filter-return #'advice-awesome-func-filter-return)
(awesome-func 1 2 3)
return value: 9

:filter-args 키워드가 인자를 수정한다면 :filter-return 키워드는 리턴 값을 수정한다.

그 외

:override, :before-while, :before-until, :after-while, :after-until 도 있다. 사용법은 3.11.3 Ways to compose advice - gnu.org 문서에서 볼 수 있다.

링크

C-x C-s C-x C-c