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.


_______________________________________________
elephant-devel site list
elephant-devel@common-lisp.net
http://common-lisp.net/mailman/listinfo/elephant-devel

Reply via email to