[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