Arc Forumnew | comments | leaders | submitlogin
Bug: on doesn't work for tables
1 point by akkartik 5599 days ago | 9 comments
From arc.arc:

  (mac on (var s . body)
    (if (is var 'index)
        (err "Can't use index as first arg to on.")
        (w/uniq gs
          `(let ,gs ,s
             (forlen index ,gs
               (let ,var (,gs index)
                 ,@body))))))
But of course you can't access tables by index. Here's how I rewrote it:

  (mac on (var expr . body)
    (if (is var 'index)
        (err "Can't use index as first arg to on.")
        `(let index 0
           (each ,var ,expr
              ,@body
              (zap [+ 1 _] index)))))
Incidentally, I see the seemingly-redundant "let ,gs" in macros all over arc.arc. Anybody know why it's there? I didn't want to propagate it without understanding what it does.


4 points by conanite 5599 days ago | link

"let ,gs ,s" frequently prevents double-evaluation of 's at run-time. It seems dumb at first when the expansion of 's is just another symbol or literal, but if 's is a more complex expression, better evaluate it just once.

-----

2 points by fallintothis 5599 days ago | link

For example, consider defining on without it.

  (mac wrong-on (var s . body)
    (if (is var 'index)
        (err "Can't use index as first arg to on.")
        `(forlen index ,s
           (let ,var (,s index)
             ,@body))))
Then

  arc> (wrong-on x (prn "abcd") (prs index x) (prn))
  abcd
  abcd
  0 a
  abcd
  1 b
  abcd
  2 c
  abcd
  3 d
  nil
because

  (macex1 '(wrong-on x (prn "abcd") (prs index x) (prn)))
is

  (forlen index (prn "abcd")
    (let x ((prn "abcd") index)
      (prs index x) (prn)))
Notice that (prn "abcd") is evaluated once at the start, then once on each iteration.

Versus the arc.arc on:

  arc> (on x (prn "abcd") (prs index x) (prn))
  abcd
  0 a
  1 b
  2 c
  3 d
  nil
because

  (macex1 '(on x (prn "abcd") (prs index x) (prn)))
is

  (let gs1763 (prn "abcd")
    (forlen index gs1763
      (let x (gs1763 index)
        (prs index x) (prn))))
Here, (prn "abcd") is evaluated only once. It's bound to the gensym gs1763, which won't clash with any variable names you already have (not strictly, cf. http://arclanguage.org/item?id=5104, but that's the idea).

You can automate this idiom with the once-only macro; see http://arclanguage.org/item?id=9918 or towards the end of http://gigamonkeys.com/book/macros-defining-your-own.html.

-----

1 point by akkartik 5599 days ago | link

Thanks for the pointer to once-only. Is this the version I should use? (I'm having trouble wrapping my head around the nested unquotes)

http://arclanguage.org/item?id=9939

My definition of on seems to eval the list only once anyway, but now I'm feeling paranoid about where else I've missed this and taken a performance hit.

-----

1 point by fallintothis 5599 days ago | link

Is this the version I should use?

You can if you want. It works.

  arc> (let a '(prn 5)
         (once-only (a)
           `(+ ,a 1)))
  (with (gs2595 (prn 5)) (+ gs2595 1))
Note that the parameter list doesn't work like w/uniq, where you can have a single argument with no parentheses. i.e.,

  arc> (let a '(prn 5)
         (once-only a
           `(+ ,a 1)))
  Error: "Can't take car of a"
This was fixed in the Anarki version (http://github.com/nex3/arc/blob/master/lib/util.arc#L374), but that version uses Anarki-specific utilities. The fix is still vanilla-Arc, if you want it:

  (mac once-only (names . body)
    (withs (names (check names alist (list names))
            gensyms (map1 [uniq] names))
      `(w/uniq ,gensyms
        `(with ,(list ,@(mappend list gensyms names))
          ,(with ,(mappend list names gensyms)
            ,@body)))))

  arc> (let a '(prn 5)
         (once-only a
           `(+ ,a 1)))
  (with (gs1724 (prn 5)) (+ gs1724 1))
My definition of on seems to eval the list only once anyway

Yeah, your definition's fine because each already handles the multiple-eval situation.

Side note:

  (zap [+ 1 _] index)
is equivalent to

  (zap + index 1)
which is equivalent to

  (++ index)
Just so you know.

-----

1 point by akkartik 5599 days ago | link

D'oh. You know, I actually tried (zap ++ index) first. Don't know what I was thinking.

Thanks for the (zap + index 1) trick.

-----

1 point by fallintothis 5599 days ago | link

But of course you can't access tables by index.

You can if they're indexed by integers.

  arc> (on x (obj 0 'a 1 'b 2 'c 3 'd) (prs index x) (prn))
  0 a
  1 b
  2 c
  3 d
  nil
But using each on a table doesn't guarantee this ordering on the keys.

  arc> (each (k v) (obj 0 'a 1 'b 2 'c 3 'd) (prs k v) (prn))
  2 c
  1 b
  0 a
  3 d
  #hash((3 . d) (0 . a) (1 . b) (2 . c))
I wonder why you'd want index bound if you aren't iterating through integer keys in order. Do you have an example? Seems like there should be one, but I can't think of it.

-----

1 point by akkartik 5599 days ago | link

Yeah here's a use case. I wrote a macro called everyp to overlay atop each with a progress indicator. It prints out the number of iterations at the start and a heartbeat every n iterations. I found this bug when I tried to everyp (implemented using on) over a table.

-----

1 point by fallintothis 5599 days ago | link

Interesting. Arc has essentially that, but implements it directly with each:

  (mac noisy-each (n var val . body)
    (w/uniq (gn gc)
      `(with (,gn ,n ,gc 0)
         (each ,var ,val
           (when (multiple (++ ,gc) ,gn)
             (pr ".") 
             (flushout)
             )
           ,@body)
         (prn)
         (flushout))))
I suppose it depends on if you want index to be captured. Since on is used in your version, index is bound within the macro call. So if

  (everyp 2 c "abcd"
    (prn c)
    (++ index))
expands to

  (on c "abcd"
    (when (multiple index 2)
      (prn "heartbeat"))
    (prn c)
    (++ index))
it prints

  heartbeat
  a
  heartbeat
  c
because on captures index. This might be unexpected if you just want heartbeats in an otherwise normal each.

-----

1 point by akkartik 5599 days ago | link

Ah, thanks for the reminder about noisy-each.

-----