Hence, you get an error about unquote being undefined, because Arc doesn't actually define quasiquote or unquote as macros. They're just special identifiers that are basically parsed away before evaluation (assuming quasiquotes/unquotes are balanced). However, loading qq.arc defines an unquote macro, so instead of the error you had, you'll get this:
arc> (fancy-combine2 a b)
Error: "unquote not allowed outside of a quasiquote: a"
which makes it a bit more obvious what the error is.
For some reason, I had in my head that, inside quasiquote in a macro, you need to unquote every variable you want to evaluate before returning the code to be executed. Note that all my examples have ,sym1 and ,sym2 (except fancy-combine4, but that's deliberately executing the code before the returned generated code).
I guess the moral of the story here is that ,(...) evaluates the entire sexp, and puts the value there. I'll have to do more reading about macros.
I guess the moral of the story here is that ,(...) evaluates the entire sexp, and puts the value there.
Yup! If your quasiquoted expression only contains unquotes, the expansion is simple. Basically,
`(sexp1 sexp2 ... ,sexpn ...)
will expand into
(list 'sexp1 'sexp2 ... sexpn ...)
This also applies recursively, so that
`(sexp1 (subexpr1 ,subexpr2))
(list 'sexp1 (list 'subexpr1 subexpr2))
With unquote-splicing, you can think of quasiquote expanding into cons, so
`(sexp1 sexp2 ,@sexp3)
will expand into
(cons (list 'sexp1 'sexp2) sexp3)
If you're interested, I encourage you to look over my implementation of quasiquote in Arc. It's actually pretty simple (175 lines with comments), and might help clarify how quasiquotation works.
I'll have to do more reading about macros.
I think you seem to have the mental model for macros down. And I've always said that macros aren't that magical, because they fall out as a natural consequence of the language. Hell, here's a simple Arc "interpreter" that shows how macros are handled differently from functions:
(def interpret (expr)
(if (acons expr)
(let op (interpret (car expr))
(if (isa op 'mac) (interpret (apply (rep op) (cdr expr)))
(isa op 'fn) (apply op (map interpret (cdr expr)))
(eval expr))) ; punt on special forms (fn, if, etc.)
(def maybe-lookup-name (expr)
(if (and (isa expr 'sym) (bound expr))
Here, interpret is really just another name for eval. Macros are simply functions whose inputs are unevaluated code. They construct some other bit of unevaluated code, and that result gets evaluated.
Quasiquote is then just a nice way to construct lists, whether it's unevaluated code for a macro to return or whatever. Its syntax takes some getting used to, but it comes with practice. Happy hacking!