Arc Forumnew | comments | leaders | submitlogin
3 points by fallintothis 1888 days ago | link | parent

The error you're getting from fancy-combine2 is because you have a double-unquote. You neglected to remove the unquotes from sym1 and sym2. The correct form would be

  (mac fancy-combine (sym1 sym2)
    `(consume-symbol ,(generate-symbol sym1 sym2)))
It might help to have a user-land defined quasiquote macro, to see how it works. I implemented such a macro here:

Using qq.arc, we can see that my solution is to use this expression:

  arc> (macex1 '(quasiquote
                  (consume-symbol (unquote (generate-symbol sym1 sym2)))))
  (list (quote consume-symbol) (generate-symbol sym1 sym2))
On the other hand, your version is like this:

  arc> (macex1 '(quasiquote
                    (unquote (generate-symbol (unquote sym1)
                                              (unquote sym2))))))
  (list (quote consume-symbol) (generate-symbol (unquote sym1) (unquote sym2)))
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.

4 points by zck 1887 days ago | link

Ah, fantastic! Thanks for the hint.

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.


4 points by fallintothis 1887 days ago | link

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))
expands into

  (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.)
        (maybe-lookup-name expr)))

  (def maybe-lookup-name (expr)
    (if (and (isa expr 'sym) (bound expr))
        (eval 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!