2

I want to write an emacs macro via defmacro that takes a parameter which could either be (1) a constant string or (2) a symbol whose value is a string.

Within the macro definition I want to be able to distinghish between alternatives (1) and (2) above but I am finding this difficult to do.

My first attempt is:

  (defmacro my-macro (x)
    `(message (if (stringp ,x) "given input is a string" "given input is not a string")))

When I run it with a constant string as input I get

(my-macro "this is s atring")
 ⇒ "given input is a string"

which is what I expected, since we are under situation (1), however

(progn
 (setq s "this is s atring")
 (my-macro s)
 )
 ⇒ "given input is a string"

which is not what I expected as we are under situation (2). I then tried:

 (defmacro my-macro-2 (x)
    `(message (if (symbolp ,x) "given input is a symbol" "given input is not a symbol")))

When I run it with a constant string input I get

(my-macro-2 "this is s atring")
     ⇒ "given input is not a symbol"

which is OK but

(progn
  (setq s "this is s atring")
  (my-macro-2 s)
  )
     ⇒ "given input is not a symbol"

which is not OK.

So the question is then:

Question. How to distinguish from within a macro definition whether a parameter is a constant string, as opposed to a symbol whose value is a string?

Ruy
  • 787
  • 4
  • 11
  • General tip: in all such cases, your first question should be: Do I really want a Lisp macro here, or should I be using a function? In most cases, a function is what you really want. The rule you should use is: if a function could work, then a Lisp macro is the wrong approach. Lisp macros are for automatically writing code or "programmable programming". Even experienced Lispers can have trouble keeping track of when evaluation takes place for different parts of a macro, which is your problem here, as the answers below explain. – Phil Hudson Jan 18 '23 at 08:28
  • Dear @Phil, thanks for your comment. However let me explain my point of view since that might also be a useful tip for you. When I (and probably also lots of other people) suspect a bug in a piece of code, that bug is often hidden within a very complex construct making it hard to even pinpoint its general location. My practice is to strip down as much code as possible, constantly testing to make sure the apparent bug is still there. Once you do that, the problem spot will become evident and the solution is almost always easy to find. That is precisely what motivated my question ... – Ruy Jan 19 '23 at 13:35
  • ... and also the reason the code I included in my question is so short. You would certainly not be happy if I presented you with lots of code and simply ask 'Please find a bug here'. In conclusion, when you see someone asking a question, I believe the right approach is to address the question as it is rather than doubting the OP's ability to correctly formulate their question. – Ruy Jan 19 '23 at 13:35
  • 1
    The very first words of your post are "I want to write an emacs macro". You don't say why. Can you? I mean why specifically a Lisp *macro* and not a *function*? Why do you need code to be generated at a different time than when it is run? That's what a Lisp macro is for. If you have a good answer then maybe you really do have the right tool in mind with Lisp macros. If you don't, then you probably should be using a function, not a macro. – Phil Hudson Jan 20 '23 at 11:38
  • Thanks for worrying, @Phil! I do have a good reason to write a macro but it is entirely irrelavant for my question, so that is why I did not include it. – Ruy Jan 20 '23 at 13:43

2 Answers2

3

You’re substituting the argument into code which is then evaluated. The evaluated result is a string in both cases, as you have observed. Test it before you generate the code instead:

(defmacro my-macro (x)
    (let ((m (if (stringp x)
                 "given input is a string"
                 "given input is not a string")))
        `(message ,m)))

This is known as running code at compile time, rather than at run time.

db48x
  • 15,741
  • 1
  • 19
  • 23
2

Remember that a macro uses its arguments to construct Lisp code, which is then evaluated. Use macroexpand to see the intermediate code.

(defmacro my-macro (x)
  `(message (if (stringp ,x) "given input is a string" "given input is not a string")))
my-macro
(macroexpand '(my-macro "this is a string"))
(message (if (stringp "this is a string") "given input is a string" "given input is not a string"))
(macroexpand '(my-macro s))
(message (if (stringp s) "given input is a string" "given input is not a string"))

Notice how the test on the type of the argument is part of the constructed intermediate code, not part of the construction of the intermediate code like you intended. You need to unquote that test.

(defmacro my-macro-3 (x)
  `(message ,(if (stringp x) "given input is a string" "given input is not a string")))
my-macro-3
(macroexpand '(my-macro-3 "this is a string"))
(message "given input is a string")
(my-macro-3 "this is a string")
"given input is a string"
(macroexpand '(my-macro-3 s))
(message "given input is not a string")
(my-macro-3 s)
"given input is not a string"
  • Thanks for a very clear explanation! I am opting for the first answer given since I cannot accept both :-( – Ruy Jan 12 '23 at 01:20