[elephant-devel] set-valued slots review

Alex Mizrahi alex.mizrahi at gmail.com
Mon Sep 12 15:26:18 UTC 2011


I. RATIONALE

There is a number of cases where one might want to store more than one 
value in a slot. An example from our testassociations: person can hold 
multiple jobs, job can have multiple holders.

There is a number of ways how you can hold these multiple values in a 
single slot:

1. Lisp collection like list or array. It is serialized as a whole, so 
you need to be careful to update slot after manipulations. If there are 
many items and list is frequently updated it is less than perfect.
2. Associations: it is, perhaps, ideal when you have relationship 
between classes. (I'll cover it in another message.)
3. pset (or btree): just assign pset instance to slot, then use pset API 
functions on it.
4. New slot-valued slot feature.

So how what is it? It is very similar to storing pset in a slot, and in 
fact it is implemented as a pset under the hood, with a number of 
differences:

1. There are convenience macros like set-insert and set-remove which 
combine slot-value with pset function. But you can use them on 
pset-in-a-slot too, so it is not really a difference.

2. On-demand ("lazy") initialization -- object is created with an 
unbound slot and it is created on first access. So it can save space 
(and time) if set is empty.

3. Pset storage is automatically reclaimed on slot-makunbound.

4. "Fancy" (setf (things-of obj) a-thing) API.

5. Special handling of assigning slot-set and NIL to a slot.

6. In theory we can implement optimized storage (on per-backend basis) 
or add more convenience features.

To be honest, I think that just using pset is good enough and features 
above do not add much. But, on the other hand, it is nice to have an 
official solution for storing multiple values in a slot. So, as it is 
already implemented, I think we should keep it and improve functionality 
and consistency.

II. API

You can use set-valued slots in three possible ways:

1. Macro API:
    (set-insert 1 object 'numbers)
    (set-remove 2 object 'numbers)
    (set-list object 'numbers) => (1)

2. Accessor/pset API:
    (insert-item 1 (numbers-of object))
    (remove-item 2 (numbers-of object))
    (find-item 1 (numbers-of object)) => t (generalized boolean)
    (slot-set-list (numbers-of object)) => (1)
    (map-slot-set (lambda (number) (print number))
          (numbers-of object))

3.
     (setf (numbers-of object) 1)
     (setf (numbers-of object) 2)
     (slot-set-list (numbers-of object)) = (1 2)

I find option `2` the best option -- code is readable and it is natural.
Macro-based is slightly shorter, but you have to quote slot name and it 
is somewhat unusual. I'm not really sure we need it, but perhaps it is 
better to keep it for people who do not like accessors.

I'm in favor of removing option 3 because it is not how SETF should work 
-- it should replace existing value, not add it. And while there are 
possible ways to improve it, I don't think it makes sense to do that 
because insert-item and friends are already good enough.

III. Weird 'features' (aka bugs)

I've mentioned that there is a special handling for the case when you 
SETF another slot-set object or NIL value. Current implementation just 
deallocates old slot-set and sets slot as usual. This actually creates a 
problem:

  * (setf (numbers-of obj) NIL) will make slot broken, none of the APIs 
would work because they assume that there is a slot-set object in a 
slot, not NIL. I guess it was just a way to wipe the set, but it doesn't 
work this way.

  * (setf (numbers-of obj1) (numbers-of obj2)) will make obj1 and obj2 
to share single slot-set. E.g. if you add something to obj1's slot it 
will appear in obj2 and vice versa.

  * (setf (numbers-of obj1) (numbers-of obj1)) will wipe all items in 
pset (and probably will make it broken too.

If we actually want to make slot-set objects transparent and employ 
"just many values in one slot" metaphor, we actually need a special 
treatment for assignment: assignment should replace contents of 
slot-set, not assign the object itself.

Thus when you do

    (setf (numbers-of obj1) (numbers-of obj2))

it should replace whatever set was in obj1 with set of obj2, but 
afterwards these slots should be independent.

As for special handling of NIL, perhaps we should just remove it 
together with a "weird API". Or maybe not, there is less harm from it.

IV. Lacking features

I think it would be great if we also add features for:

1. Initializing slot-set from list. Something like:
    (setf (numbers-of obj1) (make-slot-set :items '(1 2 3 4))

2. Wipe contents of a slot-set. You can do it via slot-makunbound, sure, 
but it is kinda ugly. (setf (numbers-of obj) NIL) is kinda weird because 
otherwise it doesn't recognize lists.
    (setf (numbers-of obj1) (make-slot-set) is, perhaps, appropriate, 
except it would create unnecessary object.





More information about the elephant-devel mailing list