Arc Forumnew | comments | leaders | submitlogin
1 point by fallintothis 5278 days ago | link | parent

Thank you for the insight. It's probably the most lucid I've been all thread. It didn't seem deliberate to me, but it could have feasibly been written that way to control other macros' expansions. This also pushes computation to expansion time, which might clarify ylando's objections about "wasting run time". Except those still confuse me: macro expansion happens once, inside a function's body or outside of it.

  arc> (mac m (expr)
         (prn "macro m has expanded")
         expr)
  #(tagged mac #<procedure: m>)
  arc> (def f (x)
         (m (+ x 1)))
  macro m has expanded
  #<procedure: f>
  arc> (f 1)
  2
But the original point seems lost because declare's story keeps changing. So, ylando: why do we need "ignores"?


1 point by ylando 5278 days ago | link

why do we need "ignores"?

Try building a macro that change global value, expand code (with macros) and then change the value back. I think that this macro must use another macro to change the value back; like the undeclare macro above. The second macro expands into unnecessary code; so if you put it inside a function this unnecessary code will waste run time. If we have "ignore" macro, we can write macros that do not produce unnecessary code.

-----

4 points by fallintothis 5277 days ago | link

I think that this macro must use another macro to change the value back; like the undeclare macro above.

Not necessarily.

  (mac declare (name prop . body)
    (let old (metadata* name)
      (= (metadata* name) prop)
      `(after (do ,@body)
         (= (metadata* ',name) ,old))))
But this introduces redundant assignments after each execution of body. Using a macro that expands into "unnecessary code" can be cheaper.

  (mac declare (name prop . body)
    (let old (metadata* name)
      (= (metadata* name) prop)
      `(after (do ,@body)
         (undeclare ,name ,old))))

  (mac undeclare (name old)
    (= (metadata* name) old)
    nil)
This introduces a redundant nil in the after block, and using after is a bit slower than just a do1. But we can't use do1 because this "do all the work at macro-expansion" approach is so touchy that it breaks:

  arc> (load "macdebug.arc") ; see http://arclanguage.org/item?id=11806
  nil
  arc> (macwalk '(declare name prop a b c))
  Expression --> (declare name prop a b c)
  macwalk> :s
  Macro Expansion ==>
    (do1 (do a b c)
         (undeclare name nil))
  macwalk> :s
  Macro Expansion ==>
    (let gs2418 (do a b c)
      (undeclare name nil)
      gs2418)
  macwalk> :s
  Macro Expansion ==>
    (with (gs2418 (do a b c))
      (undeclare name nil)
      gs2418)
  macwalk> :s
  Macro Expansion ==>
    ((fn (gs2418)
       (undeclare name nil)
       gs2418)
     (do a b c))
  macwalk> :s
  Subexpression -->
    (fn (gs2418)
      (undeclare name nil)
      gs2418)
  macwalk> :s
  Subexpression --> (undeclare name nil)
  macwalk> :s
  Value ==> nil
  Value ==> gs2418
  Value ==> (fn (gs2418) nil gs2418)
  Subexpression --> (do a b c)
  macwalk> :a
  Value ==> (do a b c)
  Value ==>
    ((fn (gs2418) nil gs2418) (do a b c))
  ((fn (gs2418) nil gs2418) (do a b c))
Note that we reach undeclare before the actual body is expanded!

We can hack it without after or do1 (or mutation, but I avoid that anyway).

  (mac declare (name prop . body)
    (let old (metadata* name)
      (= (metadata* name) prop)
      (w/uniq (g1 g2)
        `(with (,g1 (do ,@body)
                ,g2 (undeclare ,name ,old))
           ,g1))))

  (mac undeclare (name old)
    (= (metadata* name) old)
    nil)
This way, declare expands in the right order and we only undeclare once, since it'll expand into nil. The nil is "unnecessary", which seems to be why you want ignore, but it's a terribly pedantic point: ignore is already accomplished by dead code elimination (http://en.wikipedia.org/wiki/Dead_code_elimination). This isn't even a case of "sufficiently smart compilers" for vanilla Arc, since mzscheme already implements the standard optimizations: function inlining, dead code elimination, constant propagation/folding, etc. (see http://download.plt-scheme.org/doc/html/guide/performance.ht...) should all be able to clean up whatever ac.scm generates. E.g.,

  (mac foo ()
    `(prn ',metadata*!name))

  (declare name bar (foo))
compiles to

  ((lambda (gs581 gs582) gs581)
   ((let ((| gs581| (lambda () (ar-funcall1 _prn 'bar)))) | gs581|))
   'nil)
which should be easy to optimize.

Worrying seems premature anyway. ac.scm and arc.arc themselves do plenty worse, and the differences here are essentially rounding error.

  arc> (time10 (w/stdout (outstring)
                 (repeat 1000000
                   (with (x (do (prn 'bar))
                          y nil)
                     x))))
  time: 43048 msec.
  nil
  arc> (time10 (w/stdout (outstring)
                 (repeat 1000000
                   (prn 'bar))))
  time: 43170 msec.
  nil
Final idea: if expansion-time computation can't be avoided, you can expand the macros manually, if only for the sake of your readers. As a bonus, it does away with the dead code.

  (mac declare (name prop . body)
    (let old (metadata* name)
      (= (metadata* name) prop)
      (do1 `(do ,@(map macex-all body)) ; see http://arclanguage.org/item?id=11806
           (= (metadata* name) old))))
However, I think the fragile expansion-time computation is a bigger issue than dead code.

-----

1 point by ylando 5277 days ago | link

I try:

  (def macex-all (e)
    (zap macex e)
    (if acons.e
        (map1 macex-all e)
        e))

  (mac declare (name prop . body)
    (let old (metadata* name) 
       (= (metadata* name) prop)  
       (let exp-body (macex-all body)
            (= (metadata* name) old)
            (cons 'do exp-body))))
and it works. thanks.

-----