awwx.ws

table-vivifier

automatically create new values in a table

--- a/ac.scm	2009-12-21 10:18:00.000000000 -0500
+++ b/ac.scm	2009-12-21 11:21:21.000000000 -0500
@@ -632,6 +632,19 @@
 ; default vals with them.  To make compatible with existing written tables, 
 ; just use an atom or 3-elt list to keep the default.
 
+(define table-vivifier (make-hash-table 'weak))
+
+(define (ar-table-get fn args)
+  (ar-nill
+   (or (hash-table-get fn 
+                       (car args) 
+                       (if (pair? (cdr args)) (cadr args) #f))
+       (let ((vivifier (hash-table-get table-vivifier fn #f)))
+         (and vivifier
+              (let ((val (vivifier)))
+                (hash-table-put! fn (car args) val)
+                val))))))
+
 (define (ar-apply fn args)
   (cond ((procedure? fn) 
          (apply fn args))
@@ -639,10 +652,7 @@
          (list-ref fn (car args)))
         ((string? fn) 
          (string-ref fn (car args)))
-        ((hash-table? fn) 
-         (ar-nill (hash-table-get fn 
-                                  (car args) 
-                                  (if (pair? (cdr args)) (cadr args) #f))))
+        ((hash-table? fn) (ar-table-get fn args))
 ; experiment: means e.g. [1] is a constant fn
 ;       ((or (number? fn) (symbol? fn)) fn)
 ; another possibility: constant in functional pos means it gets 
@@ -1064,7 +1074,10 @@
 
 (xdef table (lambda args
               (let ((h (make-hash-table 'equal)))
-                (if (pair? args) ((car args) h))
+                (if (and (pair? args) (not (ar-false? (car args))))
+                     ((car args) h))
+                (if (and (pair? args) (pair? (cdr args)))
+                     (hash-table-put! table-vivifier h (cadr args)))
                 h)))
 
 ;(xdef table (lambda args

Inspired by Perl’s autovivification feature, this hack allows new values to be automatically created in a table when a lookup doesn’t find the key already present.

In Perl, the kind of value to create is determined by syntax. Since in Arc a list reference vs. a table reference etc. has the same syntax, we’ll instead specify a function to be called to create the new value, if a key isn’t yet in a table.

arc> (= x (table nil (fn () 0)))
#hash()
arc> (++ x!a)
1
arc> x
#hash((a . 1))
arc> (= y (table nil (fn () (table))))
#hash()
arc> (set y!a!b)
t
arc> y
#hash((a . #hash((b . t))))

Unlike having a default value for the table, the vivified value is always stored in the table.

arc> (= z (table nil (fn () 0)))
#hash()
arc> z!a
0
arc> z
#hash((a . 0))

This means it doesn't make sense to use this for tables where you want to be able to check whether a value is already in the table or not.

Get this hack

Using the hackinator:

$ hack \
    ycombinator.com/arc/arc3.1.tar \
    awwx.ws/table-vivifier0.patch

unLicense

This patch is in the public domain. The combination of this patch and Arc may be redistributed or modified under the same terms as Arc itself.

Contact me

Twitter: awwx
Email: andrew.wilcox [at] gmail.com