[PEAK] Trellis: propagating constants

Sergey Schetinin maluke at gmail.com
Tue Apr 14 18:58:36 EDT 2009

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
>>> c1, c2
(Constant(1), LazyCell(<function <lambda> at 0x00B6CBB0>, 1 [inactive]))
>>> c2.value
>>> 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

            for listener in self.iter_listeners():
                if isinstance(listener, ReadOnlyCell):
            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

            link = self.next_listener
            while link is not None:
                nxt = link.next_listener
                listener = link()
                if isinstance(listener, ReadOnlyCell):
                on_undo(stm.Link, self, listener)
                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)
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>
  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
  File "c:\files\checkouts\trellis\peak\events\stm.py", line 221, in cleanup
  File "c:\files\checkouts\trellis\peak\events\stm.py", line 260, in rollback_to
  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):

with this:

                if isinstance(listener, ReadOnlyCell) and
listener.next_subject is None:

and now the test passes. In fact it has two fixes at once :) Either
would suffice:

                if isinstance(listener, ReadOnlyCell):

                if isinstance(listener, ReadOnlyCell) and
listener.next_subject is None:

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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: const.patch
Type: application/octet-stream
Size: 934 bytes
Desc: not available
Url : http://www.eby-sarna.com/pipermail/peak/attachments/20090415/b9f87cc4/const.obj

More information about the PEAK mailing list