Wednesday, 7 November 2007

The power of Common Lisp macros

I have started looking at Hunchentoot as I threatened to do in an earlier post. The app I'm going to use to benchmark the framework is a blogging app. Going pretty well so far, I must say.

One of the functions I am using is decode-universal-time. It returns multiple values which you can liken to tuples. For example:


CL-USER> (decode-universal-time (get-universal-time))
31
59
19
7
11
2007
2
NIL
8

The usual way of dealing with these types of functions is to use multiple-value-bind which essentially creates bindings for each value. However, this usually means that you have to bind all the values, even if you don't want to:

CL-USER> (multiple-value-bind
(second minute hour date)
(decode-universal-time (get-universal-time))
(print date))
7

So you can see that just to get at the date value we had to bind three other value. Not only is this annoying but SBCL complains about it:

; in: LAMBDA NIL
; FUNCTION
;
; caught STYLE-WARNING:
; The variable SECOND is defined but never used.
;
; caught STYLE-WARNING:
; The variable MINUTE is defined but never used.
;
; caught STYLE-WARNING:
; The variable HOUR is defined but never used.
;
; compilation unit finished
; caught 3 STYLE-WARNING conditions

Argh!

In Python, one can write:

_,_,_,date = decode_universal_time(get_universal_time)

Which is also known as destructuring assignment (by me!)

So ideally, I would have loved to write:

(multiple-value-bind (_ _ _ date) (decode-universal-time (get-universal-time)) (print date))

Whats that? Smells like a language extension? Ah, you mean a macro, what Lisp has had natively since forever. Thanks to some help from the fine people on #lisp, I have written my first useful macro:

(defmacro multiple-value-bind-2 (bindlist value-form &body body)
(multiple-value-bind
(ignores-list args-list)
(make-gensym-and-fixed-bindlist bindlist)
`(multiple-value-bind
,args-list
,value-form
(declare (ignore ,@ignores-list))
,@body)))

; Take a list of symbols that may contain _, and return two lists
; first being a list of the symbols generated, the second being a list
; of the _ replaced with said generated symbols.
; (make-gensyn-and-fixed-bindlist '(a b _ d)) =>
; (:#G1023)
; (a b :#G1023 d)
(defun make-gensym-and-fixed-bindlist (bindlist)
(let* (gensym-list
(fixed-bindlist (loop for item in bindlist
when (eq '_ item)
collect (car (push (gensym) gensym-list))
else collect item)))
(values gensym-list fixed-bindlist)))

I called it multiple-value-bind-2 because I have no idea how Lisp packages work yet and don't know how to create my own that exports this macro. Here is example usage:

CL-USER> (multiple-value-bind-2
(_ _ _ date)
(decode-universal-time (get-universal-time))
date)
7


Pretty damn sweet.

I apologize for the bad formatting, I have not yet figured out how to get blogger to let me put code snippets :-(

Update: Figured out that using the <pre> tag does the job wrt code snippets.

3 comments:

Deus said...

Even simpler, to get the date
(nth 3 (get-decoded-time))
--> 3 (for Wednesday)

Sohail Somani said...

Indeed! Still, I find destructuring bind/assignment to be a lot more readable and verifiable from a glance. Also, my example was to only get one component but for sure, with destructuring bind, you can pull out more than one.

grault said...

You can also say things like

(defun example (c) (declare (ignorable c)) 1)