Many ways to skin a GNU
Posted on Tue 24 July 2012 by alex in geek
It's time for another examination of Emacs LISP and a selection of ways to solve the same problem. In this case it's the behaviour of the etags-select package. This handy function will present a selection table when it finds multiple definitions of the same tag. This is handy if your global TAGs file contains reference to multiple binaries that might have a common code heritage. It even provides a handy function etags-select-find-tag-at-point which will check point to see if that is a tag. However if point is on a blank line I'd prefer it to just prompt me for a tag name.
First version:
(defun my-naive-find-tag () "Find at point or fall back" (interactive) (unless (etags-select-find-tag-at-point) (etags-select-find-tag)))
Unfortunately this fails rather badly. I naively assumed etags-select-find-tag-at-point would return 'nil on failure. Instead it bombs out with an error because etags-select-find expects a parameter and when find-tag-default fails it errors out.
Second version:
(defun my-working-find-tag() "Find tag and call etags-select-find-tag-at-point if it will work" (interactive) (if (find-tag-default) (etags-select-find-tag-at-point) (etags-select-find-tag)))
This works by checking find-tag-default will work before calling etags-select-find-tag-at-point. Of course there is some duplication here because find-tag-default will get called again once I know it will work. Dissatisfied I asked the stackoverflow community for suggestions. The first solution is to simply trap the error case.
Third version:
(defun my-try-except-find-tag() "Find at point or fall to etags-select-find-tag on error" (interactive) (unless (ignore-errors (or (etags-select-find-tag-at-point) 't)) (etags-select-find-tag)))
This works by utilising deep lisp black magic to stop the error propagating and returning a 'nil if it does. The (or (etags-select-find-tag-at-point) 't) line is to ensure a successful call returns something so we don't then fall through. Interestingly the comments around subr.el mentions some of the keywords used may be redefined by common lisp.
Forth version:
(defun my-efficent-find-tag() "Find tag at point, caching find-tag-default" (interactive) (let ((ftd (find-tag-default))) (flet ((find-tag-default () ftd)) (if (find-tag-default) (etags-select-find-tag-at-point) (etags-select-find-tag)))))
While I expected something like ignore-errors to exist this demonstrates the flexibility of dynamic languages like Emacs Lisp. The key is the use of flet to redefine find-tag-default so when it gets executed again inside etags-select-find-tag-at-point it simply returns the cached value.
So as usual with these posts I try to invite feedback. Which of these forms do you prefer? Would you solve the problem another way? Have you just learnt something new about Emacs Lisp?