4

Here's an example:

(defmacro test (arg)
  `(message foo-,arg))

(defmacro test-with-space (arg)
  `(message foo- ,arg))

(macroexpand-1 '(test-with-space bar))
(macroexpand-1 '(test bar))

Both eval to (message foo -bar). Intuitively to me it should eval to (message foo-bar) and (message foo -bar) respectively. Am I using backquotes incorrectly or is this a feature? If not, is there a way to do what I'm trying to do without polluting code with intern and format?

  • It's the way things are (a feature). And it has nothing to do with macros. `(toto titi,4), `(toto titi,"tata"). – Drew Oct 19 '16 at 14:57
  • Right, backquotes and macros are used so often together though. So in other words if you want symbol foo-bar in your lisp macros and you have foo and bar concat those strings and intern that string? It would make so much sense for the above to work though:( – Russ Kiselev Oct 19 '16 at 15:19
  • Actually, the behavior is strange. But, I think the rational is that comma start new token. What you want could've been accomplished if Emacs Lisp had reader macros (i.e. it could manipulate the basic syntax of the reader before it starts interpreting the parse). – wvxvw Oct 19 '16 at 16:44
  • 3
    You seem to be expecting foo-,arg to be the same thing as (intern (concat "foo-" (symbol-name ',arg))), but there's absolutely no reason why that should be the case. ,arg evaluates arg to whatever arbitrary lisp object it has as its value, so your expectation might not even be valid in the first place. – phils Oct 19 '16 at 20:44
  • 1
    In any case, note that the lisp reader has to read your code before it gets evaluated. For your expectation to work, it would need to read all of foo-,arg into a form which would subsequently do the interning at evaluation time. I don't think it's particularly surprising that it doesn't attempt to do that. – phils Oct 19 '16 at 20:56
7

The short answer is that this is expected behaviour.

You seem to be expecting foo-,arg to be something like (intern (concat "foo-" (symbol-name ',arg))), but there's no reason why that should be the case.

For starters ,arg evaluates arg to whatever arbitrary lisp object it has as its value, so there mightn't even be a sensible way to form the end of a symbol name from that value.

Moreover, note that the lisp reader has to read your code into objects before they can be evaluated. For your expectation to work, it would need to read all of foo-,arg into a form which would subsequently do the interning at evaluation time.

In practice foo- is read as a symbol, and ,arg is read into a form recognised by the backquote processing.

We can call the reader ourselves to see what it actually produces (or rather, we can see the printed representation of those lisp objects; internally those objects are not text, but they are converted back into text for display purposes).

(read "(defmacro test (arg) `(message foo-,arg))")
=> (defmacro test (arg) (\` (message foo- (\, arg))))

Here we see that foo-,arg has been read into two forms, represented as foo- and (\, arg).

The list structure (\, arg) is recognised by backquote-process

(backquote-process '(message foo-,arg))
=> (1 list (quote message) (quote foo-) arg)

Naturally we can also see this form in the definition of the test macro:

(symbol-function 'test)
=> (macro lambda (arg) (list (quote message) (quote foo-) arg))

To get the result you were after, the reader would need to have produced something along these lines:

(read "(defmacro test (arg) `(message foo-,arg))")
=> (defmacro test (arg) (\` (message ,(intern (concat "foo-" (symbol-name (eval arg)))))))
  • Great answer, I'm not the OP but learned a lot reading it, thanks! Knowing how to examine what happens "under the hood" is invaluable to discover things yourself. Not just posting the solution but posting the way how to discover things are the best kind of answers to help others learning. It would be nice to have a tag for these kind of answers which explain things like that. – clemera Oct 20 '16 at 12:02
1

Not that I'm advocating something like this...

(defmacro with-intern-concat (&rest body)
  (declare (indent 0) (debug t))
  (cons 'progn (intern-concat-map body)))

(defun intern-concat-map (body)
  (with-temp-buffer
    (intern-concat-map-qq-symbols body
      (lambda (symbol)
        (erase-buffer)
        (save-excursion (insert (symbol-name symbol)))
        (while (re-search-forward "\\([^%]+\\)\\|%\\([^%]*\\)%" nil t)
          (replace-match
           (if (match-string 1)
               (prin1-to-string (match-string 1))
             (concat "(symbol-name " (match-string 2) ")"))
           t t))
        `(intern (concat ,@(read (concat "(" (buffer-string) ")"))))))))

(defun intern-concat-map-qq-symbols (form fn)
  (declare (indent 1))
  (cond
   ((and (consp form)
         (eq '\, (car form))
         (= (length form) 2)
         (symbolp (cadr form)))
    (list (car form) (funcall fn (cadr form))))
   ((consp form)
    (cons (intern-concat-map-qq-symbols (car form) fn)
          (and (cdr form) (intern-concat-map-qq-symbols (cdr form) fn))))
   (t form)))

(defmacro test (arg)
  (with-intern-concat
    `(message "%d" ,fill-%arg%)))

(macroexpand '(test column))
;; => (message "%d" fill-column)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.