awwx.ws

label0

Add labels to values

http://awwx.ws/label0.arc:

(def ltable-name (lname)
  (sym (+ "label-table-" lname "*")))

(mac getlabel (lname obj)
  `((scheme hash-table-get) ,(ltable-name lname) ,obj nil))

(mac setlabel (lname obj val)
  (w/uniq gval
    `(ret ,gval ,val
       ((scheme hash-table-put!) ,(ltable-name lname) ,obj ,gval))))

(mac deflabel (lname)
  `(do (= ,(ltable-name lname) (scheme (make-hash-table 'weak 'eqv)))
       (def ,lname (obj)
         (getlabel ,lname obj))
       (defset ,lname (x)
         (w/uniq g
           (list (list g x)
                 `(getlabel ,',lname ,g)
                 `(fn (val) (setlabel ,',lname ,g val)))))))

This hack allows you to (conceptually) attach pieces of information to Arc values, as if you were putting a label on them.

For example, suppose we were implementing something like palsecam’s persistent tables, and so we wanted to be able to label particular tables as being persistent.

arc> (= x (table))
#hash()
arc> (= y (table))
#hash()
arc> (deflabel persistent)
#<procedure>
arc> (set (persistent x))
t
arc> (persistent x)
t
arc> (persistent y)
nil

We’ve created a label called “persistent”, and we’ve labeled the table x with having the persistent value t. y hasn’t been labeled, so it’s persistent value defaults to nil.

Now we can have a function like sref do something different if it’s passed a persistent object:

arc> (def save-object (x)
       (prn "saving object: " x))
#<procedure: save-object>
arc> (extend sref (com val ind) (persistent com)
       (do1 (orig com val ind)
            (save-object com)))
#<procedure: sref>
arc> (= (x 'foo) 3)
saving object: #hash((foo . 3))
3
arc> (= (y 'foo) 3)
3

Labels are attached to the values themselves, irrespective of how the values’ contents may change. For example, consider two different lists that contain the same items:

arc> (= x '(a b c))
(a b c)
arc> (= y '(a b c))
(a b c)

If we use one of these lists as a key in a table, we can use either one to lookup the value, because table lookups are done by the contents of the key, not the identity of the key.

arc> (let a (table)
       (= (a x) 'foo)
       (a y))
foo

However labeling one of the lists doesn’t affect a label on the other list.

arc> (deflabel color)
#<procedure>
arc> (= (color x) 'green)
green
arc> (= (color y) 'blue)
blue
arc> (color x)
green
arc> (color y)
blue

(the label is actually connected to the first cons cell of the list).

Conversely, changing a list means that it won’t look up the same value in a table anymore, but its label will be unchanged. (As long as we’re looking at the label on the same initial cons cell, of course).

arc> (wipe (cdr y))
nil
arc> x
(a b c)
arc> y
(a)
arc> (let a (table)
            (= (a x) 'foo)
            (a y))
nil
arc> (color x)
green
arc> (color y)
blue

Labels are a poor choice when you simply want to add more data to a data structure you already have, you’re better off using Arc’s standard data structures like tables and lists for that.

I expect that labels are going to be useful when we want to have orthogonal type information, the kind of thing that in object oriented programming you’d perhaps use multiple inheritance for, but using labels does the job in a less complicated way. Thus in our first example a value can be a list or a table or a string etc., and it can also be a persistent value or a normal non-persistent value.

Being able to independently label a value as persistent simplifies my code, since I don’t have to figure out how to make that “persistent” attribute part of the value’s data or base type.

Prerequisites

This hack depends on arc3.1, scheme0, and ret0.

License

Same as Arc.

Contact me

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