I know about html.arc, the point of the exercise is not to replace the builtin html macros, but to learn how to write macros (and how to think in Arc).
I have a couple of questions now:
1. Are macros defined in terms of 'eval'?
If there's such a thing as an anonymous function, why shouldn't there be an anonymous macro? Is eval the closest thing to anonymous macros?
2. Why would I want to my def-tags macro to be defined in terms of 'do'?
More generally, what the hell is the point of 'do' anyway? I would never have thought of doing anything inside a 'do' block, it seems rather redundant.
Note: I'm not trying to ridicule 'do', I'm just expressing my utter lack of understanding.
Btw, your guess of my code is pretty accurate, except I used string instead of +
(def tag (name body)
(string "<" name ">" body "</" name ">"))
Notice how the argument 'abcdef was quoted. Macros don't evaluate their arguments, but the code they generate might (e.g., if bar was a function, it'd try to evaluate abcdef as a variable).
They aren't actually implemented that way. eval operates at run-time and doesn't have access to lexical variables. Macros expand at compile-time, so it's as if you had the expansion there to begin with. E.g.,
arc> (let y 10
(eval 'y))
Error: "reference to undefined identifier: _y"
but
arc> (mac foo (arg) arg)
#3(tagged mac #<procedure: foo>)
arc> (let y 10
(foo y))
10
because
(let y 10
(foo y))
macroexpands into
(let y 10
y)
before it ever runs.
Anonymous macros are plausible, but they might force work to be done at run-time -- essentially, you're right that eval's the closest thing to it. But since macros happen at compile-time, you can't do something like
((if (mem 'x stuff) push pull) 'y stuff)
The compiler sees the macros push and pull, but they're not applied to anything, so it doesn't expand them. Then at run-time, you get an error as it tries to evaluate each form. You have a macro trying to act like a function (i.e., at run-time).
2. do is for when you want to execute a series of expressions, but do it in just one s-expression (i.e., the thing wrapped in do). You see it a lot in macros; e.g., defs in arc.arc:
which is just one list. You couldn't return multiple values, so the macro couldn't expand into
(def f (x) (+ x 1))
(def g (y) (- y 1))
directly.
Another place you see do a lot is in if statements. Each clause is one expression, but if you want to do multiple things in one expression, you need the do. E.g.,
(if test
(do (pr #\t) (pr #\h) (pr #\e) (pr #\n))
else)
will print "then" if test is true, otherwise it'll do else. This wouldn't work without the do:
(if test ; if "test"
(pr #\t) ; print #\t
(pr #\h) ; else if (pr #\h)
(pr #\e) ; then (pr #\e)
(pr #\n) ; else if (pr #\n)
else) ; then "else"
P.S. That's the return value of the statement.
arc> (pr "a") ; prints "a" WITHOUT a newline, then returns the first thing it
; printed (the string "a")
a"a"
arc> (prn "a") ; print "a" WITH a newline, then returns the first thing it
; printed (the string "a")
a
"a"
That's not always true. If a programming system supported first class macros, then an anonymous macro could be passed in as an argument to a function, which could then apply it in a more sophisticated way at run time. I.e. apply it to a list of objects that don't even exist at compile time.
However, since arc does not support first-class macros, and neither do most compiled languages that I'm aware of, you're basically correct.