Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Common Lisp Backquote/Backtick: How to Use?

I am having trouble with Lisp's backquote read macro. Whenever I try to write a macro that seems to require the use of embedded backquotes (e.g., ``(w ,x ,,y) from Paul Graham's ANSI Common Lisp, page 399), I cannot figure out how to write my code in a way that compiles. Typically, my code receives a whole chain of errors preceded with "Comma not inside a backquote." Can someone provide some guidelines for how I can write code that will evaluate properly?

As an example, I currently need a macro which takes a form that describes a rule in the form of '(function-name column-index value) and generates a predicate lambda body to determine whether the element indexed by column-index for a particular row satisfies the rule. If I called this macro with the rule '(< 1 2), I would want a lambda body that looks like the following to be generated:

(lambda (row)
  (< (svref row 1) 2))

The best stab I can make at this is as follows:

(defmacro row-satisfies-rule (rule)
  (let ((x (gensym)))
    `(let ((,x ,rule))
       (lambda (row)
         (`,(car ,x) (svref row `,(cadr ,x)) `,(caddr ,x))))))

Upon evaluation, SBCL spews the following error report:

; in: ROW-SATISFIES-RULE '(< 1 2)
;     ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121))
; 
; caught ERROR:
;   illegal function call

;     (LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121)))
; ==>
;   #'(LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121)))
; 
; caught STYLE-WARNING:
;   The variable ROW is defined but never used.

;     (LET ((#:G1121 '(< 1 2)))
;       (LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121))))
; 
; caught STYLE-WARNING:
;   The variable #:G1121 is defined but never used.
; 
; compilation unit finished
;   caught 1 ERROR condition
;   caught 2 STYLE-WARNING conditions
#<FUNCTION (LAMBDA (ROW)) {2497F245}>

How can I write macros to generate the code I need, and in particular, how do I implement row-satisfies-rule?


Using the ideas from Ivijay and discipulus, I have modified the macro so that it compiles and works, even allowing forms to be passed as the arguments. It runs a bit differently from my originally planned macro since I determined that including row as an argument made for smoother code. However, it is ugly as sin. Does anyone know how to clean it up so it performs the same without the call to eval?

(defmacro row-satisfies-rule-p (row rule)
  (let ((x (gensym))
        (y (gensym)))
    `(let ((,x ,row)
           (,y ,rule))
       (destructuring-bind (a b c) ,y
         (eval `(,a (svref ,,x ,b) ,c))))))

Also, an explanation of clean, Lispy ways to get macros to generate code to properly evaluate the arguments at runtime would be greatly appreciated.

like image 887
sadakatsu Avatar asked Jun 24 '11 23:06

sadakatsu


2 Answers

One thing to understand is that the backquote feature is completely unrelated to macros. It can be used for list creation. Since source code usually consists of lists, it may be handy in macros.

CL-USER 4 > `((+ 1 2) ,(+ 2 3))
((+ 1 2) 5)

The backquote introduces a quoted list. The comma does the unquote: the expression after the comma is evaluated and the result inserted. The comma belongs to the backquote: the comma is only valid inside a backquote expression.

Note also that this is strictly a feature of the Lisp reader.

Above is basically similar to:

CL-USER 5 > (list '(+ 1 2) (+ 2 3))
((+ 1 2) 5)

This creates a new list with the first expression (not evaluated, because quoted) and the result of the second expression.

Why does Lisp provide backquote notation?

Because it provides a simple template mechanism when one wants to create lists where most of the elements are not evaluated, but a few are. Additionally the backquoted list looks similar to the result list.

like image 183
Rainer Joswig Avatar answered Nov 03 '22 03:11

Rainer Joswig


First of all, Lisp macros have "destructuring" argument lists. This is a nice feature that means instead of having an argument list (rule) and then taking it apart with (car rule) (cadr rule) (caddr rule), you can simply make the argument list ((function-name column-index value)). That way the macro expects a list of three elements as an argument, and each element of the list is then bound to the corresponding symbol in the arguemnt list. You can use this or not, but it's usually more convenient.

Next, `, doesn't actually do anything, because the backquote tells Lisp not to evaluate the following expression and the comma tells it to evaluate it after all. I think you meant just ,(car x), which evaluates (car x). This isn't a problem anyway if you use destructuring arguments.

And since you're not introducing any new variables in the macro expansion, I don't think (gensym) is necessary in this case.

So we can rewrite the macro like this:

(defmacro row-satisfies-rule ((function-name column-index value))
  `(lambda (row)
     (,function-name (svref row ,column-index) ,value)))

Which expands just how you wanted:

(macroexpand-1 '(row-satisfies-rule (< 1 2)))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))

Hope this helps!


If you need the argument to be evaluated to get the rule set, then here's a nice way to do it:

(defmacro row-satisfies-rule (rule)
  (destructuring-bind (function-name column-index value) (eval rule)
    `(lambda (row)
       (,function-name (svref row ,column-index) ,value))))

Here's an example:

(let ((rules '((< 1 2) (> 3 4))))
  (macroexpand-1 '(row-satisfies-rule (car rules))))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))

just like before.


If you want to include row in the macro and have it give you your answer straightaway instead of making a function to do that, try this:

(defmacro row-satisfies-rule-p (row rule)
  (destructuring-bind (function-name column-index value) rule
    `(,function-name (svref ,row ,column-index) ,value)))

Or if you need to evaluate the rule argument (e.g. passing '(< 1 2) or (car rules) instead of (< 1 2)) then just use (destructuring-bind (function-name column-index value) (eval rule)


Actually, a function seems more appropriate than a macro for what you're trying to do. Simply

(defun row-satisfies-rule-p (row rule)
  (destructuring-bind (function-name column-index value) rule
    (funcall function-name (svref row column-index) value)))

works the same way as the macro and is much neater, without all the backquoting mess to worry about.

In general, it's bad Lisp style to use macros for things that can be accomplished by functions.

like image 12
smackcrane Avatar answered Nov 03 '22 01:11

smackcrane