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?

Drew
  • 75,699
  • 9
  • 109
  • 225
Russ Kiselev
  • 464
  • 3
  • 10
  • 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
  • I agree. I actually went ahead and wanted to file an emacs-bug for it to initiate discussion on gnu lists but once again got put off with the fact that my email isn't setup in emacs. – Russ Kiselev Oct 19 '16 at 17:53
  • 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
  • What @phil said (both points). – Drew Oct 19 '16 at 21:43
  • BTW, you can still use `M-x report-emacs-bug`, even if you don't use Emacs for mail. It can use your mail client. (E.g., I use MS Outlook.) – Drew Oct 19 '16 at 21:43
  • @phils Thank you for taking your time to explain this to me! First point makes sense. I don't understand the second point but it's because I don't really understand how backquote magic works. Drew I didn't know that, thanks. – Russ Kiselev Oct 20 '16 at 04:44
  • I've elaborated on this somewhat in an answer. Spending some time researching and understanding the difference between the *read* and *eval* phases of lisp would be time well spent, IMO. – phils Oct 20 '16 at 09:07

2 Answers2

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)))))))
phils
  • 48,657
  • 3
  • 76
  • 115
  • 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)
politza
  • 3,316
  • 14
  • 16