Trellis: propagating constants
Sergey Schetinin <[email protected]> Wed, 15 Apr 2009 01:58:36 +0300
| Newsgroups | gmane.comp.python.peak |
|---|---|
| Message-ID | <[email protected]> |
Some Trellis cell types know they can become constants but that
"constness" doesn't propagate immediately. Illustration:
>>> from peak.events.trellis import LazyCell
>>> c1 = LazyCell(lambda: 1)
>>> c2 = LazyCell(lambda: c1.value)
>>> c2.value
1
>>> c1, c2
(Constant(1), LazyCell(<function <lambda> at 0x00B6CBB0>, 1 [inactive]))
>>> c2.value
1
>>> c1, c2
(Constant(1), Constant(1))
The problem is that if c2 had listeners it would never become
constant, because on_commit(self._check_const) is called from run
method and that would never get called as there are no subjects.
The reason for the whole thing is that _check_const only runs if rule
didn't read any cells and c2 did read c1. So the solution seems to
iterate through listeners and check their constness as well, like
this:
for listener in self.iter_listeners():
if isinstance(listener, ReadOnlyCell):
on_commit(listener._check_const)
change_attr(self, 'next_listener', None)
That doesn't work either because the link is not broken from the
listener's side because it is stored in undo log (change_attr(self,
'next_listener', None)), so the unlink wasn't called yet. So lets try
this:
link = self.next_listener
while link is not None:
nxt = link.next_listener
listener = link()
if isinstance(listener, ReadOnlyCell):
on_commit(listener._check_const)
on_undo(stm.Link, self, listener)
link.unlink()
link = nxt
It worked, so I decided to put together a little bigger test:
from peak.events import trellis
c1 = trellis.LazyCell(lambda: 1)
c2 = trellis.LazyCell(c1.get_value)
c3 = trellis.LazyCell(c1.get_value)
c4 = trellis.LazyCell(lambda: (c2.value, c3.value))
c5 = trellis.Cell(c4.get_value)
c5.value
assert (c1.__class__ is c2.__class__
is c3.__class__ is c4.__class__
is trellis.LazyConstant)
and it failed with
Traceback (most recent call last):
File "check_autoconst.py", line 7, in <module>
c5.value
File "c:\files\checkouts\trellis\peak\events\trellis.py", line 192,
in get_value
atomically(schedule, self)
File "c:\files\checkouts\trellis\peak\events\stm.py", line 467, in atomically
return super(Controller,self).atomically(self._process, func, args, kw)
File "c:\files\checkouts\trellis\peak\events\stm.py", line 187, in atomically
self.cleanup(*sys.exc_info())
File "c:\files\checkouts\trellis\peak\events\stm.py", line 221, in cleanup
self.rollback_to(0)
File "c:\files\checkouts\trellis\peak\events\stm.py", line 260, in rollback_to
f(*a)
File "c:\files\checkouts\trellis\peak\events\trellis.py", line 255,
in __setattr__
raise AttributeError("Constants can't be changed", self)
AttributeError: ("Constants can't be changed", Constant((1, 1)))
Which means something was rolling back and trying to set _set_by to
NO_VALUE (seen in debug prints). I guess the reason is that
c4._check_const is in commit queue twice (added by both c2 and c3), so
I have an idea: replace this
if isinstance(listener, ReadOnlyCell):
on_commit(listener._check_const)
with this:
if isinstance(listener, ReadOnlyCell) and
listener.next_subject is None:
listener._check_const()
and now the test passes. In fact it has two fixes at once :) Either
would suffice:
if isinstance(listener, ReadOnlyCell):
listener._check_const()
or
if isinstance(listener, ReadOnlyCell) and
listener.next_subject is None:
on_commit(listener._check_const)
The former seems a little better as there are no assumptions on what
are the criteria for constness, but on the other hand can there be any
criteria not requiring absence of subjects? And on_commit seems
unnecessary anyway, so left both of them in.
See patch in attachment and test above.
--
Best Regards,
Sergey Schetinin
http://s3bk.com/ -- S3 Backup
http://word-to-html.com/ -- Word to HTML Converter
_______________________________________________
PEAK mailing list
[email protected]
http://www.eby-sarna.com/mailman/listinfo/peak
const.patch
(application/octet-stream, 935 B)
Index: trellis.py
===================================================================
--- trellis.py (revision 2599)
+++ trellis.py (working copy)
@@ -207,9 +219,17 @@
if self.next_subject is None and (
self._reset is _sentinel or self._value==self._reset
):
+ link = self.next_listener
+ while link is not None:
+ nxt = link.next_listener
+ listener = link()
+ if isinstance(listener, ReadOnlyCell) and listener.next_subject is None:
+ listener._check_const()
+ on_undo(stm.Link, self, listener)
+ link.unlink()
+ link = nxt
change_attr(self, '_set_by', _sentinel)
change_attr(self, 'rule', None)
- change_attr(self, 'next_listener', None)
change_attr(self, '__class__', self._const_class())
def _const_class(self):