'Macro string Interpolation / How to unquote inside string?

I have a macro like:

(defmacro test [x] `"~x")

After running it expands to the following:

=> (test abc)
'~x'

Why doesn't it expand to 'abc'?

What is the correct method to unquote inside a string?

hy


Solution 1:[1]

I'm confused why you expected this to work, and I'm unclear what you want. ~ in your example is in a string literal, so it's an ordinary string character, not the ~ operator, the same way that ((fn [x] "x") 5) returns "x" and not "5".

If you want to stringify the model that you're passing into a macro, use str, like this:

hy 1.0a1+114.g2abb33b1 using CPython(default) 3.8.6 on Linux
=> (defmacro test [x] (str x))
<function test at 0x7fe0476144c0>
=> (test abc)
"abc"

Solution 2:[2]

I think there is a more general question here: how can I use string interpolation at macro-expansion time?

The answer is to remember that the unquote prefix ~ can be applied to any form, not just bare symbols, and that it is simply shorthand for unquote.

So you can just use an f-string, and unquote the f-string. Using the original example:

(defmacro test [x]
  (quasiquote (unquote f"{x}")))

Or using the shorthand prefix symbols:

(defmacro test [x]
  `~f"{x}")

~f"{module} ..." is valid shorthand for `(unquote f"{module} ...").

As another example of using arbitrary forms with ~, you could use str.format instead of an f-string:

(defmacro test [x]
  `~(.format "{}" x))

As a real-world example, here is a macro I wrote today:

(import sys
  typing [NoReturn])

(defn ^NoReturn die [^int status ^str message]
  (print message :file sys.stderr)
  (sys.exit status))

(defmacro try-import [module]
  `(try
     (import ~module)
     (except [ImportError]
       (die 1 ~f"{module} is not available, exiting."))))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kodiologist
Solution 2 shadowtalker