2012/03/18: advising functions (in emacs)

Recently I stumbled across a quite bizzare feature of emacs lisp: defadvice. Essentially, the idea is that you want to change the behaviour of a function and only focus on the change, not the function.

There are a couple of places where this could be desirable. Say, you have an emacs-Package and it would be just more useful if one function behaved slightly differently, e.g., might use some prefix. So what? Of course, the best thing would be, if the author had thought about such potential desires and provided a hook. But things are not always perfect. Next option would be to copy the whole function source and modify it. But then you're essentially forking (OK, only a single function, but still, your change could be much less) which is not nice maintenance wise. Or that function might actually be written in a different language!

That's were advice comes in. Essentially, every function has implicit advise hooks, before, after or around the execution -- and you can even manipulate the arguments! Let's look at a more mathematical example.



(setq fibcounter 0)

(defun fib (n)
  (setq fibcounter (+ 1 fibcounter))
  (cond ((eq 0 n) 1)
        ((eq 1 n) 1)
        (t (+ (fib (- n 1))
              (fib (- n 2))))))
download

It works. Things evaluate as expected.

   (setq fibcounter 0)
   (fib 6)              ;; 13
   fibcounter           ;; 25
   (fib 6)              ;; 13
   fibcounter           ;; 50

However, this implementation is horribly inefficient! Let's use memoization to speed things up.



(setq fibremembered '())

(defun rememberfib (fn n)
  (setq fibremembered (cons (cons n  fn) fibremembered)))

(defadvice fib (around fib-memo activate)
  (let* ((n (ad-get-arg 0))
         (lookup (assoc n fibremembered)))
    (if lookup
        (setq ad-return-value (cdr lookup))
      (rememberfib ad-do-it n))))
download

Now the same expressions evaluate differently.

   (setq fibcounter 0)
   (setq fibremembered '())
   (fib 6)                     ;; 13
   fibcounter                  ;;  7
   fibremembered               ;; ((6 . 13) (5 . 8) (4 . 5) (3 . 3) (2 . 2) (0 . 1) (1 . 1))
   (fib 6)                     ;; 13
   fibcounter                  ;;  7

There's a peculiarity to keep in mind. The return-value of an around-advised function is not the value of the around advice, but the value of the variable ad-return-value at the end of the advice. If you call ad-do-it, this value will be set (but can be modified later on!).

I still can't decide whether I like this feature. I didn't need it so far, as the world of hooks just works too nicely. Thanks to all thinking about the right hooks!



Cross-referenced by: