Saturday, February 21, 2009

A little lithp

After almost 20 years of fairly hardcore Emacs love, I finally wrote my first Lisp macro. And now I want to smear my good fortune all over you, dear reader, by recounting my experience in more depth.

How lucky for you!

About Lisp...

Created in 1958, Lisp is the 2nd oldest (not to mention sexiest) programming language in widespread use today. Its syntax is simple: lists of tokens contained within parentheses. In Lisp, code and data are represented the same way: as lists of tokens, duh. This deceptively powerful quality is what allows "Lispers" to create new syntax, i.e. Domain Specific Languages, within Lisp itself.

Lisp is basically what the "executable XML geniuses" -- yes, I'm looking at you Ant, Spring, J2EE, VoiceXML, and countless others -- should have used! XML is what you use when your chosen language for manipulating data can't adequately represent it.

My Macro

Ok, enough with the history. Here's the macro:

(require 'sqlplus)
(defmacro def-oracle (name connect-string)
(let* ((buf-name (concat "oracle-" (symbol-name name)))
(fun-name (intern buf-name)))
`(defun ,fun-name ()
(interactive)
(sqlplus ,connect-string ,buf-name))))
Lisp macros are like functions, except that instead of computing a value, they compute another Lisp expression that will compute the value. In this case, my macro creates a defun expression, which defines a function. Simple, huh?

The trick to macros is the backquote character: `. Combined with the comma character, they allow you to selectively evaluate elements of a quoted list. In my macro, the list whose first token is defun is quoted (by a backquote) and my "selectively evaluated elements" are fun-name, connect-string, and buf-name.

Here's how I invoke my macro:
(def-oracle dev   "user/password@dev.host:port/sid")
(def-oracle qa "user/password@qa.host:port/sid")
(def-oracle prod "user/password@prod.host:port/sid")
After those lines execute, three new functions are created:
  • oracle-dev
  • oracle-qa
  • oracle-prod
Ta-da!

Um, hello?

Your stolid reaction belies the awesomeness of my macro!

Maybe I should've started with my reasons for creating the macro in the first place.
  • Create a handy function that "wraps" the Emacs sqlplus function for each of the databases to which I must often connect
  • Don't prompt me for the dang connection credentials every time I invoke the handy wrapper function
  • Enforce a consistent naming convention for the various sqlplus buffers I have open so that I may, for example, easily switch between qa and dev
  • Make it easy to add more of these handy wrapper functions
There. Is that better? I certainly think so. :-)

But perhaps it would be enlightening to see what I would've had to code to achieve the same result without using a macro:

(defun oracle-dev ()
(interactive)
(sqlplus "user/password@dev.host:port/sid" "oracle-dev"))
(defun oracle-qa ()
(interactive)
(sqlplus "user/password@qa.host:port/sid" "oracle-qa"))
(defun oracle-prod ()
(interactive)
(sqlplus "user/password@prod.host:port/sid" "oracle-prod"))

See how verbose and redundant that is? It's positively VERBOSIDUNDANT!!! And my naming/calling conventions are sprinkled all over the place. To change any one would require multiple redundant changes. And that's not DRY, and that's not good.