[PEAK] Messages to the future (in the Trellis)
Phillip J. Eby
pje at telecommunity.com
Thu Jul 26 11:57:43 EDT 2007
Now that the high-level API is complete and I've started work on
doc/test refactoring, I've started thinking about supporting mutable
data structures such as lists, sets, and dictionaries.
In the curent Trellis model, you can't detect when such things have
changed; you have to use custom objects (similar to ZODB's
PersistentList and PersistentDict) that can track their own changes.
However, implementing such structures correctly is rather tricky,
especially the mutation parts.
Within a given Trellis pulse, values aren't allowed to actually
change, because then different rules might see different values. So,
when you implement a mutable data structure, it has to defer making
any actual changes until the next trellis pulse.
In the API examples I've written previously, I've used a number of
different hacks to handle this, including my recent "hub/spoke" examples.
But after a lot of thought, it seems to me that what we need is a
simple way to manage "messages to the future". For example, here's a
simple Set implementation:
class Set(trellis.Component):
added = trellis.todo(lambda self: set())
removed = trellis.todo(lambda self: set())
to_add = added.future
to_remove = removed.future
@trellis.rule
def data(self):
data = self.data
if data is None:
data = set()
if self.removed:
for item in self.removed:
data.remove(item)
if self.added:
for item in self.added:
data.add(item)
return data
def __iter__(self):
return iter(self.data)
def __len__(self):
return len(self.data)
def __contains__(self, item):
return item in self.data
def add(self, item):
if item in self.to_remove:
self.to_remove.remove(item)
else:
self.to_add.add(item)
def remove(self, item):
if item in self.to_add:
self.to_add.remove(item)
else:
self.to_remove.add(item)
To work, it requires a new feature: "todo". The idea is that any
attribute marked "todo" is a data structure containing changes to be
made to the object, and you can also use it to create a "future"
version of the same attribute. This "future" version is a read-only
property that lets you access the *future* version of the "todo" attribute.
If there is no future version available yet, a new value is
calculated using the todo attribute's rule.
Effectively, a 'todo' creates a 'receiver' cell whose default value
is created using the supplied rule. Accessing its future
automatically assigns a fresh value to it, if needed. And presto, we
can now operate on our future events directly, by simply referring to
their future versions. And, since the todo's are receivers, they
automatically reset to their empty value after everyone has seen the changes.
This makes it fairly simple to create mutable data structures, since
one can simply design whatever change records are appropriate (such
as added/removed in the case of a set), and have the manipulation
methods manipulate this future state. Rules for the current state
can then apply the changes, so that any attempt to read the current
state, brings it up to date with the planned changes.
At this point, I'm not 100% certain this is the way to go, but it
looks quite promising. I'll have to see how useful the approach is
for other data types, though. For dictionaries, one could do
something like updated/removed, or added/changed/removed. Lists are
harder to describe changes to, however. Perhaps the best thing would
just be a list of co-ordinates and replacement slices, since all list
manipulation can be described in terms of setslice operations.
One other thing that's needed, that 'todo' doesn't provide, is any
way to tell that the object itself has "changed", so that if you use
one of these structures in another cell, any dependent rules can be
updated. Overall, however, in such a case you should probably just
refer directly to the change events, rather than treating the data
structure as if the whole thing changed.
More information about the PEAK
mailing list