| I've never been quite comfortable with how macros work under the hood. That's the root cause of this bug. I have a macro that macroexpands into code containing, among other things, another macro call. That's fine. One of the arguments the inner macro expects is a symbol. However, I want this symbol to be generated based on input to the outer macro. I've reduced my code to this test case to show what I want. Here's a function that takes inputs and simply combines them into a symbol: (def generate-symbol (symbol1 symbol2)
(sym (string symbol1 #\. symbol2)))
Cool, this works easily enough: arc> (generate-symbol 'a 'b)
a.b
Here's the inner macro. I've made it really simple; obviously my real code does more than this. Its main purpose here is to take a bare unquoted symbol and return a symbol: (mac consume-symbol (symbol)
`',symbol)
And its use: arc> (consume-symbol arst)
arst
I want to write a macro that takes two unquoted symbols, combine them, then call consume-symbol on them. So this code: (fancy-combine a b)
should end up with this return value: 'a.b
Here's my first cut: (mac fancy-combine (sym1 sym2)
`(consume-symbol (generate-symbol ,sym1
,sym2)))
But it doesn't quite work: arc> (fancy-combine a b)
(generate-symbol a b)
Here's why: arc> (macex1 '(fancy-combine a b))
(consume-symbol (generate-symbol a b))
Ok, this makes sense: whatever we have as the argument to consume-symbol will be taken as the symbol, and not evaluated: that's how macros work.So how can we get it to do what I want? We can't just throw on another unquote: (mac fancy-combine2 (sym1 sym2)
`(consume-symbol ,(generate-symbol ,sym1
,sym2)))
#(tagged mac #<procedure: fancy-combine2>)
arc> (fancy-combine2 a b)
Error: "_unquote: undefined;\n cannot reference undefined identifier"
Calculating it outside of the quasiquote works, but I'm uncomfortable, because I'm not 100% sure when exactly things are evaluated: generate-symbol is run at macroexpansion-time, right? But then how does that interact with having different values passed to this new fancy-combine arc> (mac fancy-combine3 (sym1 sym2)
(let symbol-to-consume (generate-symbol sym1 sym2)
`(consume-symbol ,symbol-to-consume)))
#(tagged mac #<procedure: fancy-combine3>)
arc> (fancy-combine3 a b)
a.b
Similarly, we can edit fancy-combine2 slightly to have a nested quasiquote, and this works, but it feels hacky to me. I don't like having the nested quasiquotes right next to the symbols, but I can't quite explain why. It just feels like a code smell. arc> (mac fancy-combine4 (sym1 sym2)
`(consume-symbol ,(generate-symbol `,sym1
`,sym2)))
#(tagged mac #<procedure: fancy-combine4>)
arc> (fancy-combine4 a b)
a.b
It feels better to move the inner quasiquote out, like this: (mac fancy-combine5 (sym1 sym2)
`(consume-symbol ,`(generate-symbol ,sym1
,sym2)))
But that doesn't work: arc> (fancy-combine5 a b)
(generate-symbol a b)
So, which way would you prefer to write this? What am I missing about macroexpansion and quasiquotation that would make this understandable? |