[Subversion] / Trellis / Internals.txt  

View of /Trellis/Internals.txt

Parent Directory | Revision Log
Revision: 2436 - (download)
Fri Nov 30 22:46:43 2007 UTC (16 years, 5 months ago) by pje
File size: 23833 byte(s)
The new algorithm lands - version bump to 0.6a1dev.
Too many changes to list!
Cell Properties
---------------

Cell properties are descriptors used to implement cell-based attributes.  They
are created using a name::

    >>> from peak.events import trellis, activity
    >>> from peak.events.trellis import Cell, CellProperty

    >>> CellProperty('C')
    CellProperty('C')

And they compare equal only to other cell properties with the same name::

    >>> CellProperty('C') == CellProperty('C')
    True
    >>> CellProperty('C') != CellProperty('F')
    True
    >>> CellProperty('C') != 'C'
    True
    >>> CellProperty('C') == CellProperty('F')
    False
    >>> CellProperty('C') != CellProperty('C')
    False
    >>> CellProperty('C') == 'C'
    False

When used as descriptors in a class, they read or write the ``.value``
attribute of the corresponding cell in the object's ``__cells__`` dictionary::

    >>> class Converter(object):
    ...     C = CellProperty('C')
    ...     F = CellProperty('F')
    ...     def __init__(self):
    ...         self.__cells__ = dict(
    ...             F = Cell(lambda: self.C * 1.8 + 32, 32),
    ...             C = Cell(lambda: (self.F - 32)/1.8, 0),
    ...         )

    >>> Converter.C
    CellProperty('C')

    >>> tc = Converter()
    >>> tc.C
    0.0
    >>> tc.F
    32.0

    >>> tc.C = 100
    >>> tc.F
    212.0

Setting a CellProperty-mediated attribute to a ``Cell`` instance, replaces
that instance in the ``__cells__`` dictionary::

    >>> tc.F = Cell(lambda: -tc.C)
    >>> tc.F
    -100

Getting or setting a cell attribute that has no cell in the object's
``__cells__`` invokes the registered factory function for cell creation::

    >>> from peak.events.trellis import CellFactories
    >>> def demo_factory(typ, ob, name):
    ...     print "Creating", name, "cell for", typ, "instance"
    ...     return Cell(lambda: ob.C*2, 0)

    >>> CellFactories(Converter)['F'] = demo_factory
    >>> del tc.__cells__['F']
    >>> tc.F
    Creating F cell for <class 'Converter'> instance
    200
    >>> tc.F = 42

    >>> del tc.__cells__['F']
    >>> tc.F = 27
    Creating F cell for <class 'Converter'> instance
    >>> tc.F
    27

    >>> tc.F = Cell(lambda: -tc.C)
    >>> tc.F
    -100

Cell factories are inherited by subclasses::

    >>> class Converter2(Converter):
    ...     pass
    >>> tc2 = Converter2()
    >>> del tc2.__cells__['F']
    >>> tc2.F = -40
    Creating F cell for <class 'Converter2'> instance
    >>> tc2.F
    -40


TodoProperty
------------

TodoProperty objects are like CellProperty, but offer an extra ``future``
attribute::

    >>> from peak.events.trellis import TodoProperty, todo_factory

    >>> class X(object):
    ...     x = TodoProperty('x')
    ...     future_x = x.future
    >>> help(X)
    Help on class X in module __builtin__:
    <BLANKLINE>
    class X(object)
     |  ...
     |  future_x
     |      The future value of the 'x' attribute...
     |  x...
     |      Property representing a ``todo`` attribute
    <BLANKLINE>


    >>> TodoProperty('x')==TodoProperty('y')
    False
    >>> TodoProperty('x')==CellProperty('x')
    False
    >>> TodoProperty('x')!=TodoProperty('x')
    False

    >>> TodoProperty('x')==TodoProperty('x')
    True
    >>> TodoProperty('x')!=TodoProperty('y')
    True
    >>> TodoProperty('x')!=CellProperty('x')
    True


Class Metadata
--------------

    >>> from peak.events.trellis import CellRules, CellValues, IsDiscrete
    >>> from peak.events.trellis import IsOptional, default_factory, NO_VALUE
    >>> from peak.events.trellis import *

Setting a value in CellRules or CellValues sets the same value in CellFactories
to default_factory::

    >>> class Test(object):
    ...     x = rule(lambda self: 27)
    ...     values(y = 42)

    >>> CellRules(Test)
    {'x': <function <lambda> at ...>}

    >>> CellValues(Test)
    {'y': 42}

    >>> CellFactories(Test)
    {'y': <function default_factory at ...>,
     'x': <function default_factory at ...>}

    >>> Test.x
    CellProperty('x')

    >>> Test.y
    CellProperty('y')

    >>> default_factory(Test, Test(), 'y')
    Value(42)

    >>> t = Test()
    >>> t.__cells__ = {}

    >>> t.x
    27

    >>> t.__cells__['x']
    Constant(27)


IsOptional and IsDiscrete default to false for any attribute that has an
explicit setting defined in a given class in any other registry besides
themselves::

    >>> IsOptional(Test)
    {'y': False, 'x': False}

    >>> IsDiscrete(Test)
    {'y': False, 'x': False}

The default_factory handles _sentinel values by not passing them to the Cell
constructor::

    >>> CellRules(Test)['x'] = None
    >>> CellValues(Test)['x'] = NO_VALUE
    >>> default_factory(Test, Test(), 'x')
    Value(None)

And it binds non-None rules to the instance::

    >>> CellRules(Test)['x'] = lambda self: 42
    >>> default_factory(Test, Test(), 'x')
    ReadOnlyCell(<bound method Test.<lambda> of <Test object at...>>, None [uninitialized])

And uses the event flag from IsDiscrete::

    >>> CellRules(Test)['x'] = None
    >>> IsDiscrete(Test)['x'] = True
    >>> default_factory(Test, Test(), 'x')
    Value(None, discrete[None])

The todo_factory only uses the rule to create the default value, and always
creates a rule-free receiver, regardless of the value or receiver flag
settings::

    >>> CellRules(Test)['x'] = lambda self: dict()
    >>> CellValues(Test)['x'] = 54
    >>> IsDiscrete(Test)['x'] = False

    >>> Test.x = TodoProperty('x')
    >>> Test.to_x = Test.x.future

    >>> todo_factory(Test, Test(), 'x')
    Value({}, discrete[{}])


future, @modifier, etc.
-----------------------

    >>> t = Test()
    >>> t.__cells__ = {}

    >>> def dump(): print "t.x =", t.x

    >>> watcher = Cell(dump)
    >>> watcher.value
    t.x = {}

    >>> t.to_x
    Traceback (most recent call last):
      ...
    RuntimeError: future can only be accessed from a @modifier

    >>> def add(key, val):
    ...     t.to_x[key] = val
    >>> add = modifier(add)

    >>> add(1, 2)
    t.x = {1: 2}

    >>> def update(**kw):
    ...     for k, v in kw.items():
    ...         add(k, v)

    >>> update(x=1, y=2)
    t.x = {'y': 2}
    t.x = {'x': 1}

    >>> update = modifier(update)

    >>> update(x=1, y=2)    # it all happens in one go
    t.x = {'y': 2, 'x': 1}


task_factory
------------

The task_factory creates a TaskCell::

    >>> from peak.events.trellis import task_factory
    >>> def f(self): yield 1; print "done"
    >>> CellRules(Test)['x'] = f
    >>> CellValues(Test)['x'] = NO_VALUE
    >>> IsDiscrete(Test)['x'] = False
    >>> t = task_factory(Test, Test(), 'x')
    >>> t
    TaskCell(None)
    >>> print t.value
    None
    >>> activity.EventLoop.flush()  # yield 1
    >>> activity.EventLoop.flush()  # 'print done'
    done


Decorators
----------

    >>> from peak.util.decorators import decorate

    >>> class Test:
    ...     def aRule(self):
    ...         return 42
    ...     r = rule(aRule)   # trick to exercise auto-name-finding
    ...     anEvent = receiver(-1)
    ...     optRule = optional(lambda:99)
    ...     todo = todo(lambda self:{})

    >>> Test.r
    CellProperty('aRule')

    >>> Test.todo
    TodoProperty('todo')

    >>> CellRules(Test)
    {'anEvent': None,
     'optRule': <function <lambda> at...>,
     'aRule': <function aRule at...>, 'todo': <function <lambda> at ...>}

    >>> CellValues(Test)
    {'anEvent': -1, 'todo': None}

    >>> IsDiscrete(Test)
    {'anEvent': True, 'optRule': False, 'aRule': False, 'todo': True}

    >>> CellFactories(Test)
    {'anEvent': <function default_factory...>,
     'aRule': <function default_factory...>,
     'todo': <function todo_factory...>}

    >>> IsOptional(Test)
    {'anEvent': False, 'optRule': True, 'aRule': True, 'todo': False}


    >>> class Test(object):
    ...     todos(
    ...         added   = lambda self:{},
    ...         removed = lambda self:set()
    ...     )
    ...     to_add = added.future
    ...     to_remove = removed.future
    ...     decorate(task)
    ...     def task(self): yield None

    >>> CellRules(Test)
    {'added': <function <lambda> at ...>,
     'removed': <function <lambda> at...>,
     'task': <function task at ...>}

    >>> CellValues(Test)
    {'removed': None, 'added': None}

    >>> IsDiscrete(Test)
    {'added': True, 'removed': True, 'task': False}

    >>> CellFactories(Test)
    {'added': <function todo_factory...>,
     'removed': <function todo_factory...>,
     'task': <function task_factory...>}


Error messages::

    >>> class Test:
    ...     decorate(rule, optional)
    ...     def wrong(): pass
    Traceback (most recent call last):
      ...
    TypeError: @rule decorator must wrap a function directly

    >>> class Test:
    ...     decorate(trellis.task, trellis.optional)
    ...     def wrong(): pass
    Traceback (most recent call last):
      ...
    TypeError: @task decorator must wrap a function directly

    >>> class Test:
    ...     decorate(todo, optional)
    ...     def wrong(): pass
    Traceback (most recent call last):
      ...
    TypeError: @todo decorator must wrap a function directly


Components
----------

    >>> def hello(msg):
    ...     print msg

    >>> class Test(Component):
    ...     rules(
    ...         X = lambda self: self.Y + 2
    ...     )
    ...     receivers(Y = 0)
    ...     Z = value(0)
    ...     def always(self):
    ...         print "always!"
    ...     always = rule(always)
    ...     def only_on_request(self):
    ...         print "hello!"
    ...     only_on_request = optional(only_on_request)
    ...     A=optional(lambda s:hello("A!"))
    ...     rules(B=lambda s:hello("B!"))


    >>> IsDiscrete(Test)
    {'A': False, 'B': False, 'always': False, 'only_on_request': False,
     'Y': True, 'X': False, 'Z': False}


Non-optional attributes are activated at creation time, as are the appropriate
cells::

    >>> t = Test()
    B!
    always!

    >>> t.__cells__.keys()
    ['Y', 'always', 'Z', 'B', 'X']

    >>> t.only_on_request
    hello!

    >>> t.A
    A!
    >>> t.A

    >>> t.X
    2
    >>> t.Y = 23
    >>> t.X
    2
    >>> t.Z = 1
    >>> t.X
    2

    >>> def show_X():
    ...     print "X =", t.X
    >>> show_X = Cell(show_X)
    >>> show_X.value
    X = 2

    >>> t.Y = 23
    X = 25
    X = 2

    >>> t.__cells__.keys()
    ['A', 'B', 'always', 'only_on_request', 'Y', 'X', 'Z']

    >>> del show_X

Keyword arguments are accepted by the constructor::

    >>> t = Test(always=Constant(False), B=Constant(0), Z=55)
    >>> t.Z
    55
    >>> t.B
    0
    >>> t.always
    False

But not for undefined attributes::

    >>> t = Test(qqqq=42)
    Traceback (most recent call last):
      ...
    TypeError: Test() has no keyword argument 'qqqq'


Creating a component from within a rule should not create a dependency link to
the rule::

    >>> x = Cell(value=27)
    >>> class Test(Component):
    ...     rules(x = lambda self: x.value)
    >>> def _rule():
    ...     print "creating"
    ...     Test()
    >>> r = Cell(_rule)
    >>> r.value
    creating
    >>> x.value = 99

And initializing a component cell that would ordinarily be read-only, should
replace it with a constant::

    >>> class Test(Component):
    ...     rules(x = lambda self: {})
    >>> t = Test(x=())
    >>> t.__cells__['x']
    Constant(())

XXX better error message for write to read-only cell


A component should not create cells for attributes that are not cell properties
(e.g., due to override in a subclass)::

    >>> class Test(Component):
    ...     decorate(rule)
    ...     def attr(self):
    ...         print "computing"

    >>> Test()
    computing
    <Test object at ...>

    >>> class Test2(Test):
    ...     attr = None

    >>> Test2()     # attr should not be initialized
    <Test2 object at ...>


Cell Objects
------------

get_value/set_value methods::

    >>> c = Cell()
    >>> print c.get_value()
    None
    >>> c.set_value(42)
    >>> c.value
    42
    >>> c1 = Constant(42)
    >>> c1.get_value()
    42
    >>> c1.set_value(99)
    Traceback (most recent call last):
      ...
    AttributeError: 'Constant' object has no attribute 'set_value'

    >>> c1 = Cell(lambda: c.value)
    >>> c1.get_value()
    42
    >>> c1.set_value(99)
    Traceback (most recent call last):
      ...
    AttributeError: 'ReadOnlyCell' object has no attribute 'set_value'

    >>> c.set_value(99)
    >>> c1.get_value()
    99


Repeating, Polling, Recalc
--------------------------

    >>> poll()
    Traceback (most recent call last):
      ...
    RuntimeError: poll() must be called from a rule

    >>> repeat()
    Traceback (most recent call last):
      ...
    RuntimeError: repeat() must be called from a rule


"Poll" re-invokes a rule on the next recalc of *anything*::

    >>> c2 = Cell(value=99)
    >>> def count():
    ...     if poll(): print "calculating"
    ...     return c.value+1

    >>> c = Cell(count, 0)

    >>> def hello(): print "c =", c.value
    >>> c1 = ObserverCell(hello)
    c = calculating
    1
    >>> c.value
    1
    >>> c.value
    1

    >>> c2.value = 16
    calculating
    c = 2

    >>> c2.value = 7
    calculating
    c = 3

    >>> c2.value = 66
    calculating
    c = 4


At least, until/unless you get rid of the cell::

    >>> c = Cell(value=99)
    >>> c.value
    99

Which of course requires that its listeners drop their references first::

    >>> c2.value = 27
    calculating
    c = 99

And now the repeated polling of the now-vanished cell stops::

    >>> Cell().value = 66
    >>> c.value = 20
    c = 20


Discrete Processing
-------------------

    >>> class LineReceiver(trellis.Component):
    ...     bytes = trellis.receiver('')
    ...     delimiter = trellis.value('\r\n')
    ...     _buffer = ''
    ...
    ...     decorate(trellis.discrete)
    ...     def line(self):
    ...         buffer = self._buffer = self._buffer + self.bytes
    ...         lines = buffer.split(self.delimiter, 1)
    ...         if len(lines)>1:
    ...             buffer = self._buffer = lines[1]
    ...             trellis.repeat()
    ...             return lines[0]
    ...
    ...     decorate(trellis.rule)
    ...     def dump(self):
    ...         if self.line is not None:
    ...             print "Line:", self.line

    >>> trellis.IsDiscrete(LineReceiver)['line']
    True

    >>> lp = LineReceiver()
    >>> lp.bytes = 'xyz'
    >>> lp.bytes = '\r'
    >>> lp.bytes = '\n'
    Line: xyz

    >>> lp.bytes = "abcdef\r\nghijkl\r\nmnopq"
    Line: abcdef
    Line: ghijkl

    >>> lp.bytes = "FOObarFOObazFOOspam\n"
    >>> lp.delimiter = "FOO"
    Line: mnopq
    Line: bar
    Line: baz

    >>> lp.delimiter = "\n"
    Line: spam

    >>> lp.bytes = 'abc\nabc\n'
    Line: abc
    Line: abc


Multitasking
------------

    >>> def raiser():
    ...     raise Exception("foo")
    ...     yield None  # make it a generator

    >>> raiser().next()
    Traceback (most recent call last):
      ...
    Exception: foo

    >>> trellis.resume()
    Traceback (most recent call last):
      ...
    RuntimeError: resume() must be called from a @trellis.task

    >>> def yielder(ob):
    ...     while not ob.bytes:
    ...         print "pausing"
    ...         yield trellis.Pause
    ...     yield ob.bytes

    >>> def t():
    ...     yield 1
    ...     yield 2
    ...     while 1:
    ...         yield yielder(lp);
    ...         print "Got:", trellis.resume()
    ...         yield trellis.Pause     # nothing changes unless we pause

    >>> c = trellis.TaskCell(t)


Dictionaries
------------

    >>> d = Dict({1:2}, a="b")
    >>> d
    {'a': 'b', 1: 2}

    >>> hash(d)
    Traceback (most recent call last):
      ...
    TypeError

    >>> d.pop(1)
    Traceback (most recent call last):
      ...
    InputConflict: Can't read and write in the same operation

    >>> d.popitem()
    Traceback (most recent call last):
      ...
    InputConflict: Can't read and write in the same operation

    >>> d.setdefault(2, 4)
    Traceback (most recent call last):
      ...
    InputConflict: Can't read and write in the same operation

    >>> del d['a']
    >>> d
    {1: 2}

    >>> def dump():
    ...     for name in 'added', 'changed', 'deleted':
    ...         if getattr(d,name):
    ...             print name, '=', getattr(d,name)
    >>> dump = ObserverCell(dump)
    >>> dump.value

    >>> d[2] = 3
    added = {2: 3}

    >>> del d[1]
    deleted = {1: 2}

    >>> del d[42]
    Traceback (most recent call last):
      ...
    KeyError: 42

    >>> d[2] = "blue"
    changed = {2: 'blue'}

    >>> d.clear()
    deleted = {2: 'blue'}

    >>> d.update({1:2}, blue=2)
    added = {'blue': 2, 1: 2}

    >>> d.update({3:4})
    added = {3: 4}

    >>> d.update(blue='shoe')
    changed = {'blue': 'shoe'}

    >>> def go(): d[99] = 42; del d[99]
    >>> modifier(go)()

    >>> def go(): d[99] = 42; d[99] = 26
    >>> modifier(go)()
    added = {99: 26}

    >>> def go(): del d[99]; d[99] = 42
    >>> modifier(go)()
    changed = {99: 42}

    >>> def go(): d[99] = 71; del d[99]
    >>> modifier(go)()
    deleted = {99: 42}

    >>> def go(): d[99] = 71; d[1] = 23; d.clear(); d.update({1:3}, a='b')
    >>> modifier(go)()
    added = {'a': 'b'}
    changed = {1: 3}
    deleted = {'blue': 'shoe', 3: 4}


Lists
-----

    >>> L = List("abc")
    >>> L
    ['a', 'b', 'c']

    >>> def dump():
    ...     if L.changed:
    ...         print "changed to", L
    >>> dump = ObserverCell(dump)
    >>> dump.value

    >>> hash(L)
    Traceback (most recent call last):
      ...
    TypeError

    >>> L.pop()
    Traceback (most recent call last):
      ...
    InputConflict: Can't read and write in the same operation

    >>> L.pop(0)
    Traceback (most recent call last):
      ...
    InputConflict: Can't read and write in the same operation

    >>> L.append(23)
    changed to ['a', 'b', 'c', 23]

    >>> L[1:2] = [3]
    changed to ['a', 3, 'c', 23]

    >>> del L[:3]
    changed to [23]

    >>> L[0] = 42
    changed to [42]

    >>> del L[0]
    changed to []

    >>> L += [1, 2]
    changed to [1, 2]

    >>> L *= 3
    changed to [1, 2, 1, 2, 1, 2]

    >>> del L[2:]
    changed to [1, 2]

    >>> L.reverse()
    changed to [2, 1]

    >>> L.remove(2)
    changed to [1]

    >>> L.insert(0, 88)
    changed to [88, 1]

    >>> L.extend( (423, -99) )
    changed to [88, 1, 423, -99]

    >>> L.sort()
    changed to [-99, 1, 88, 423]


Sets
----

    >>> try:
    ...     s = set
    ... except NameError:
    ...     from sets import Set as set

    >>> s = Set('abc')
    >>> s
    Set(['a', 'c', 'b'])

    >>> hash(s)
    Traceback (most recent call last):
      ...
    TypeError: Can't hash a Set, only an ImmutableSet.

    >>> s.pop()
    Traceback (most recent call last):
      ...
    InputConflict: Can't read and write in the same operation

    >>> def dump():
    ...     for name in 'added', 'removed':
    ...         if getattr(s, name):
    ...             print name, '=', list(getattr(s,name))
    >>> dump = ObserverCell(dump)
    >>> dump.value

    >>> s.clear()
    removed = ['a', 'c', 'b']

    >>> s.add(1)
    added = [1]

    >>> s.remove(1)
    removed = [1]

    >>> s.remove(2)
    Traceback (most recent call last):
      ...
    KeyError: 2

    >>> s.symmetric_difference_update((1,2))
    added = [1, 2]

    >>> s.difference_update((2,3))
    removed = [2]

    >>> def go(): s.add(3); s.remove(3)
    >>> modifier(go)()

    >>> def go(): s.remove(1); s.add(1)
    >>> modifier(go)()

    >>> def go(): s.add(3); s.clear()
    >>> modifier(go)()
    removed = [1]

    >>> def go(): ss=s; ss |= set([1,2]); ss &= set([2,3])
    >>> modifier(go)()
    added = [2]

    >>> def go(): ss=s; s.remove(2); ss |= set([2, 3])
    >>> modifier(go)()
    added = [3]

    >>> def go(): ss=s; ss |= set([4]); ss -= set([4, 2])
    >>> modifier(go)()
    removed = [2]

    >>> s
    Set([3])

    >>> def go(): ss=s; s.remove(3); s.add(4); ss ^= set([3, 4])
    >>> modifier(go)()

    >>> s
    Set([3])



Cell and Component Initialization
---------------------------------

This bit tests whether a rule gets to fire when a cell's value is initialized.
When you set a ruled cell's value before computing its value, the "old" value
should be the passed-in value, and the "new" value should be the assigned
value (overriding the rule)::

    >>> def aRule():
    ...     print "running", c.value
    ...     return 42

    >>> c = Cell(aRule, 96)
    >>> c.value = 27
    running 27
    >>> c.value
    27

And if the setting occurs in a modifier or action rule, the rule's execution
should be deferred until the current calculation/rule is complete, thereby
allowing several cells to be simultaneously pre-set and initialized at once::

    >>> c = trellis.Cell(aRule, 96)
    >>> def go():
    ...     c.value = 27
    ...     print "finished setting"
    >>> trellis.modifier(go)()
    finished setting
    running 27
    >>> c.value
    27


Observers::

    >>> v = trellis.Value(26)
    >>> def o():
    ...     print "value is", v.value
    >>> o = trellis.ObserverCell(o)
    value is 26
    >>> v.value = 27
    value is 27

    >>> o.layer
    Max

    >>> def fail():
    ...     v.value = 99

    >>> trellis.ObserverCell(fail)
    Traceback (most recent call last):
      ...
    RuntimeError: Can't change objects during commit phase


Pentagram of Death
------------------

The infamous "Pentagram of Death" problem is described as follows:

"""If X is an input cell, and A and B and H depend on it, and C depends on B, 
and A depends on C, and H depends on A and C, then most algorithms will 
fail to handle a situation where H is recalculated before C knows it's out 
of date."""

Let's try it.  Note that the order of values in the lambda expressions is
intended to force the dependencies to be resolved in an order that ensures H
gets told that X has changed before C does, and that C has to find out whether
B has changed before it is allowed to be recalculated::

    >>> def recalc(name):
    ...     print "calculating", name

    >>> X = trellis.Cell(value=1)
    >>> A = trellis.Cell(lambda: recalc("A") or (X.value, C.value))
    >>> B = trellis.Cell(lambda: recalc("B") or  X.value)
    >>> C = trellis.Cell(lambda: recalc("C") or (B.value, X.value))
    >>> H = trellis.Cell(lambda: recalc("H") or (X.value, C.value))

We'll calculate H first, so it will be X's first listener::

    >>> H.value
    calculating H
    calculating C
    calculating B
    (1, (1, 1))

And then A, so it'll be the last listener::

    >>> A.value
    calculating A
    (1, (1, 1))

At this point, the layers of all the cells are known::

    >>> X.layer
    0
    >>> B.layer
    1
    >>> C.layer
    2
    >>> A.layer
    3
    >>> H.layer
    3

X is at layer zero because it doesn't depend on anything.  B depends only on X,
and C depends on both X and B (so it's a higher layer).  A depends on X and C,
so it's higher than C, and H is the same.

So now, if we change X, everyone should update in the correct order::

    >>> X.value = 2
    calculating B
    calculating C
    calculating A
    calculating H

    >>> H.value
    (2, (2, 2))

If this had been a shoddy algorithm, then ``H.value`` would have been
``(2, (1, 1))`` instead, and the update order might have been different.  Note
by the way that B is calculated before C, because C depends on B as its first
dependency.  So it has to look "up" the dependency graph to see if B or X have
changed before C's rule can be run.  Since B is first in C's dependency order,
it gets recalculated first.

(Similarly, H gets recalculated before C, because its first dependency is X,
so it immediately realizes it needs to recalculate.  X also notifies H first
that a recalculation might be necessary.)


cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help