Arc Forumnew | comments | leaders | submitlogin
How about + for tables?
5 points by CatDancer 4560 days ago | 9 comments
So today I had two tables and needed to get a third table which was a combination of the two and was thinking about what to call a function to do that... "combine-tables"... "add-tables"... "+"?

Interestingly that would make + work the same way for tables as it does for association lists:

  arc> (+ '((a 1) (b 2)) '((c 3)))
  ((a 1) (b 2) (c 3))
  
  arc> (+ (obj a 1 b 2) (obj c 3))
  #hash((a . 1) (b . 2) (c . 3))
If (+ g h) added the key/value pairs to the new returned table in reverse order, h before g, then keys in g would override keys in h and the correspondence would be perfect :)


1 point by shader 4560 days ago | link

Hmmm. Interesting idea, but I think it might be a little bit nuanced for the '+ symbol. I suppose if you make it work exactly the same way that it does for alists, then it would make sense, but otherwise you might want more general database functions, so that you can do different types of compositions.

What if you wanted to add the values of the fields? I.e.:

  arc> (+ (obj a 3 b 2) (obj a 4 b 1))
  #hash((a . 7) (b . 3))

-----

1 point by Adlai 4560 days ago | link

What you describe seems to be a mapping -- you're mapping the polymorphic '+ on each pair of values in the table.

Extending the idea of the polymorphic '+ on lists to include tables suggests what CatDancer proposed in the original post.

So I wrote up the following sketchy hacks of '+ for tables:

  ; arc3/tab-tests.arc

  (def table+ tbs
    (listtab (apply +
                    (map tablist
                         (rev tbs)))))
  ;; Conceptually transparent, but try to avoid
  ;; thinking about the wastefulness...

  (def table+-better tbs
    (w/table tb-new
      (each tb (rev tbs)
        (ontable k v tb
          (= (tb-new k) v)))))
  ;; Much better! Has a nice profile too :)

  ;; The rest is a "throw-away" test for the code
  ;; (require 'human-at-monitor-to-compare-tests)
  (with (ta (obj a 'ta
                 b 'ta)
         tb (obj b 'tb
                 c 'tb)
         tc (obj c 'tc
                 a 'tc))
    (map prn `("Consecutive lines should be identical:"
               ,(table+ ta tb tc)
               ,(table+-better ta tb tc)
               ,(table+ tb tc ta)
               ,(table+-better tb tc ta)
               ,(table+ tc ta tb)
               ,(table+-better tc ta tb))))

  ; Arc REPL
  Use (quit) to quit, (tl) to return here after an interrupt.
  arc> (load "tab-tests.arc")
  Consecutive lines should be identical:
  #hash((a . ta) (b . ta) (c . tb))
  #hash((a . ta) (b . ta) (c . tb))
  #hash((a . tc) (b . tb) (c . tb))
  #hash((a . tc) (b . tb) (c . tb))
  #hash((a . tc) (b . ta) (c . tc))
  #hash((a . tc) (b . ta) (c . tc))
  nil
So, the functions work. However, to get this as the behavior of '+ for tables would require adding a clause in ac.scm, to the following definition (from line 710):

  (xdef + (lambda args
             (cond ((null? args) 0)
                   ((all string? args) 
                    (apply string-append args))
                   ((all arc-list? args) 
                    (ac-niltree (apply append (map ar-nil-terminate args))))
                   (#t (apply + args)))))
My definition uses a bunch of higher-level functions defined quite a ways into arc.arc, but I need to use them in a low-level function in ac.scm. Should I implement the functions by hand into ac.scm, or is there a better solution?

EDIT: Someday I'll manage to format my code correctly the first time around...

-----

1 point by CatDancer 4560 days ago | link

is there a better solution?

try redefining + in Arc, for example

  (redef + args
    (apply (if (all [isa _ 'table] args)
                table+-better
                orig)
           args))
where redef is

  (mac redef (name args . body)
    `(let orig ,name
       (= ,name (fn ,args ,@body))))

-----

1 point by Adlai 4559 days ago | link

I guess putting the definition of + on hash tables into arc.arc makes sense. Either one of the algorithms is a more high-level procedure than the other behaviors of '+.

I like this 'redef macro. How come it's not in arc.arc already? I can't think of any reason to not have this be there, along with the definitions of other key macros like 'do, 'def, and 'mac.

Now, granted, this function actually contributes nothing to <insert large project here, including news.yc>. However, I think that this function strikes at the heart of the "hackable language" mentality, by making it much easier to quickly modify behavior on the fly. It also sends a message to somebody reading the source code: "One of the key principles in this language is that you can redefine behavior across the board with one macro call."

I think that for quick exploratory patches, it's a better solution than editing the original definition. If I want to modify this behavior, I just have one self-explanatory (redef + ...) form to edit, rather than having to sift through the original '+ definition. It is a clean solution, which adds hackability to the language.

-----

2 points by shader 4559 days ago | link

It's already included in Anarki, which until arc3 came out was the version used by the majority of the arc community. Probably still is.

-----

1 point by CatDancer 4559 days ago | link

I like this 'redef macro

You might also like my extend macro: http://catdancer.github.com/extend.html

You know, I had completely forgotten that I had used + on tables in my example of how to use extend...!

-----

1 point by Adlai 4559 days ago | link

'extend is great! However, I think it's more of a great library/patch, than something that should sit with the core functions. 'redef, on the other hand, is elegant, transparent ('extend is transparentially challenged...), and works well for quick hacks. 'extend seems to me to be more suited for setting in stone some behavior sketched out with 'redef (for example, the method-like behavior that you described in the original thread about 'extend http://arclanguage.org/item?id=8895)

The example you have of + on tables uses the algorithm that conses up a storm...

-----

1 point by CatDancer 4559 days ago | link

One thing I like about 'extend is that I can run it several times while developing an extension without my older, buggy versions remaining on the call chain. Try actually hacking with this version of 'redef, it turns out to be really quite a pain!

  arc> (redef + args
         (apply (if (sll [isa _ 'table] args)
                     table+-better
                     orig)
           args))
oops, typo, try again

  arc> (redef + args
         (apply (if (all [isa _ 'table] args)
                     table+-better
                     orig)
           args))
oops, this 'redef is now calling the previous 'redef, I'm stuck!

On the other hand I don't think the explicit separation in 'extend between the test of whether to apply the extension from the implementation of the extension has turned out to be all that useful, so maybe some combination of the two would be better.

-----

1 point by Adlai 4558 days ago | link

Rudimentary hack to fix that:

  (= old-def (table))

  (mac redef (name args . body)
    `(let orig ,name
       (= (old-def ',name) orig)
       (= ,name (fn ,args ,@body))))

  (mac undef (name)
    `(= ,name (old-def ',name)))
I tried storing a list as each value in the table, and popping or pushing to/from the list in redef and undef, so that you could step backwards more than just one definition at a time. However, that made the entire thing much more unstable, because it turns out that 'push and 'pop rely on a bunch of functionality which breaks quite easily if you're messing with the internals.

So, this stripped down version is my little 'redef/'undef hack-pack.

-----