Arc Forumnew | comments | leaders | submitlogin
1 point by rocketnia 4966 days ago | link | parent

I'm assuming your code is a bit like this:

  (def tag (name contents)
    (+ "<" name ">" contents "</" name ">"))
  
  (mac def-tag (name)
    `(def ,name (contents)
       (tag ',name contents))
As you've figured out, (def-tag name) always expands to

  (def name (contents)
    (tag 'name contents))
even if you're not trying to define something named 'name. It's actually somewhat difficult to take a symbol you get at runtime and set a variable with that name. It can be done using (eval `(def-tag ,name)), but 'eval is usually overkill.

What you originally wanted was another macro that let you say (def-tags div span p). The first step to making that macro is figuring out what expression you want it to be an abbreviation for. In this case, it would probably be an expression like this:

  (do (def-tag div)
      (def-tag span)
      (def-tag p))
No 'each necessary. :) Once you've got an idea like that in mind, it's just a matter of picking the right tools for putting that expression together:

  (mac def-tags names
    `(do ,@(map [do `(def-tag ,_)] names)))
~~~

By the way, some of the particular names you're choosing are a bit dangerous. Arc programmers commonly use names like 'p, 'i, and 'b for local variables, specifically because they choose not to define macros with single-letter names. If you define those as macros before loading their code, then some of their function calls will be compiled as macro forms instead, and it won't be pretty.

On top of that, 'tag is already a macro that comes with Arc.

  arc> (tag foo (pr "contents"))
  <foo>contents</foo>"</foo>"
  arc> (macex1 '(tag foo (pr "contents")))
  (do (pr "<foo>") (pr "contents") (pr "</foo>"))
It comes with a bunch of other HTML-generation utilities in html.arc, like these ones...

  arc> (br 3)
  <br><br><br>
  nil
  arc> (underline:pr "contents")
  <u>contents</u>"</u>"
  arc> (hspace 4)
  <img src="s.gif" height=1 width=4>">"
  arc> (vhspace 4 9)
  <img src="s.gif" height=4 width=9>">"
  arc> (zerotable:pr "contents")
  <table border=0 cellpadding=0 cellspacing=0>contents</table>"</table>"
...which generally don't prevent people from making their own HTML libraries anyway. :-p These tools form the foundation of app.arc, blog.arc, news.arc, prompt.arc, and srv.arc, and they're not much more sophisticated than they need to be to serve those purposes.


1 point by hasenj 4965 days ago | link

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 ">"))
~~~~

P.S. What's with this?

    arc> (underline:pr "contents")
      <u>contents</u>"</u>"
What's the extra "</u>" at the end? And why shouldn't I (or should I?) worry about it?

-----

3 points by fallintothis 4965 days ago | link

1. Not exactly, but it helps to think of it that way.

  (mac foo (x)
    (list 'bar x))

  (foo abcdef)
is conceptually like

  (eval ((fn (x) (list 'bar x)) 'abcdef)) ; = (eval (list 'bar 'abcdef))
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).

This topic comes up every so often: http://arclanguage.org/item?id=11517.

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:

  (mac defs args
    `(do ,@(map [cons 'def _] (tuples args 3))))
It converts

   (defs f (x) (+ x 1)
         g (y) (- y 1))
into

   (do (def f (x) (+ x 1))
       (def g (y) (- y 1)))
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"
To learn more about macros, my debugger tool might be helpful: http://arclanguage.org/item?id=11806. Let me know if it is!

-----

1 point by hasenj 4963 days ago | link

on a second thought, the anonymous macro is nothing but the manual expansion of the macro :P

-----

1 point by shader 4962 days ago | link

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.

-----