[Subversion] / Trellis / README.txt  

Diff of /Trellis/README.txt

Parent Directory | Revision Log

version 2379, Wed Aug 1 18:22:41 2007 UTC version 2380, Tue Aug 7 20:39:50 2007 UTC
Line 52 
Line 52 
     ...         F = lambda self: self.C * 1.8 + 32,      ...         F = lambda self: self.C * 1.8 + 32,
     ...         C = lambda self: (self.F - 32)/1.8,      ...         C = lambda self: (self.F - 32)/1.8,
     ...     )      ...     )
     ...     @trellis.rule      ...     @trellis.action
     ...     def show_values(self):      ...     def show_values(self):
     ...         print "Celsius......", self.C      ...         print "Celsius......", self.C
     ...         print "Fahrenheit...", self.F      ...         print "Fahrenheit...", self.F
Line 70 
Line 70 
     Fahrenheit... -40.0      Fahrenheit... -40.0
   
 As you can see, each attribute is updated if the other one changes, and the  As you can see, each attribute is updated if the other one changes, and the
 ``show_values`` rule is invoked any time the dependent values change...  but  ``show_values`` action is invoked any time the dependent values change...  but
 not if they don't::  not if they don't::
   
     >>> tc.C = -40      >>> tc.C = -40
Line 133 
Line 133 
 ``trellis.values()`` functions inside the class body to define multiple rules  ``trellis.values()`` functions inside the class body to define multiple rules
 and values.  Or, you can use the ``@trellis.rule`` decorator to turn an  and values.  Or, you can use the ``@trellis.rule`` decorator to turn an
 individual function into a rule, or define a single value attribute by calling  individual function into a rule, or define a single value attribute by calling
 ``trellis.value``.  Here's an example that uses all of these approaches, simply  ``trellis.value``.  Last, but not least, you can use ``@trellis.action`` to
 for the sake of illustration::  define a rule that does something other than just computing a value.  Here's an
   example that uses all of these approaches, simply for the sake of
   illustration::
   
     >>> class Rectangle(trellis.Component):      >>> class Rectangle(trellis.Component):
     ...     trellis.values(      ...     trellis.values(
Line 146 
Line 148 
     ...      ...
     ...     trellis.rules(      ...     trellis.rules(
     ...         bottom = lambda self: self.top + self.height,      ...         bottom = lambda self: self.top + self.height,
     ...         right  = lambda self: self.left + self.width,  
     ...     )      ...     )
     ...      ...
     ...     @trellis.rule      ...     @trellis.rule
       ...     def right(self):
       ...         return self.left + self.width
       ...
       ...     @trellis.action
     ...     def show(self):      ...     def show(self):
     ...         print self      ...         print self
     ...      ...
Line 168 
Line 173 
     >>> r.left = 25      >>> r.left = 25
     Rectangle((25, 0), (17, 10), (42, 10))      Rectangle((25, 0), (17, 10), (42, 10))
   
 By the way, any attributes for which you define a rule (but *not* a value) will  By the way, any attributes for which you define an action or a rule (but *not*
 be read-only::  a value) will be read-only::
   
     >>> r.bottom = 99      >>> r.bottom = 99
     Traceback (most recent call last):      Traceback (most recent call last):
Line 194 
Line 199 
    named attributes.  (Note that you don't have to do this, but it often comes     named attributes.  (Note that you don't have to do this, but it often comes
    in handy.)     in handy.)
   
 3. It does a getattr() on each of the object's non-optional cell attributes,  3. It creates a cell for each of the object's non-optional cell attributes,
    in order to initialize their rules and set up their dependencies.  We'll     in order to initialize their rules and set up their dependencies.  We'll
    cover this in detail in the next section, `Automatic Activation and     cover this in more detail in the next section, `Automatic Activation and
    Dependencies`_.     Dependencies`_.
   
 In addition to doing these things another way, you can also use ``Cell``  In addition to doing these things another way, you can also use ``Cell``
Line 222 
Line 227 
    first cell has become an "observer" of the second cell.     first cell has become an "observer" of the second cell.
   
 The first of these principles explains why the rectangle printed itself  The first of these principles explains why the rectangle printed itself
 immediately: the ``show`` rule was calculated.  We can see this if we look  immediately: the ``show`` rule was calculated.  We can see this if we look at
 at the rectangle's ``show`` attribute::  the rectangle's ``show`` attribute::
   
     >>> print r.show      >>> print r.show
     None      None
Line 300 
Line 305 
   
 The ``show`` rule we've been playing with on our ``Rectangle`` class is kind of  The ``show`` rule we've been playing with on our ``Rectangle`` class is kind of
 handy for debugging, but it's kind of annoying when you don't need it.  Let's  handy for debugging, but it's kind of annoying when you don't need it.  Let's
 turn it into an "optional" rule, so that it won't run unless we ask it to::  turn it into an "optional" action, so that it won't run unless we ask it to::
   
     >>> class QuietRectangle(Rectangle):      >>> class QuietRectangle(Rectangle):
     ...     @trellis.optional      ...     @trellis.optional
       ...     @trellis.action
     ...     def show(self):      ...     def show(self):
     ...         print self      ...         print self
   
Line 352 
Line 358 
     >>> class Viewer(trellis.Component):      >>> class Viewer(trellis.Component):
     ...     trellis.values(model = None)      ...     trellis.values(model = None)
     ...      ...
     ...     @trellis.rule      ...     @trellis.action
     ...     def view_it(self):      ...     def view_it(self):
     ...         if self.model is not None:      ...         if self.model is not None:
     ...             print self.model      ...             print self.model
Line 420 
Line 426 
 `Garbage Collection`_.  `Garbage Collection`_.
   
   
   Accessing a Rule's Previous Value
   ---------------------------------
   
   Sometimes it's useful to create a rule whose value is based in part on its
   previous value.  For example, a rule that produces an average over time, or
   that ignores "noise" in an input value, by only returning a new value when the
   input changes more than a certain threshhold since the last value.  It's fairly
   easy to do this, using rules that refer to their previous value::
   
       >>> class NoiseFilter(trellis.Component):
       ...     trellis.values(
       ...         value = 0,
       ...         threshhold = 5,
       ...         filtered = 0
       ...     )
       ...     @trellis.rule
       ...     def filtered(self):
       ...         if abs(self.value - self.filtered) > self.threshhold:
       ...             return self.value
       ...         return self.filtered
   
       >>> nf = NoiseFilter()
       >>> nf.filtered
       0
       >>> nf.value = 1
       >>> nf.filtered
       0
       >>> nf.value = 6
       >>> nf.filtered
       6
       >>> nf.value = 2
       >>> nf.filtered
       6
       >>> nf.value = 10
       >>> nf.filtered
       6
       >>> nf.threshhold = 3   # changing the threshhold re-runs the filter...
       >>> nf.filtered
       10
       >>> nf.value = -3
       >>> nf.filtered
       -3
   
   As you can see, referring to the value of a cell from inside the rule that
   computes the value of that cell, will return the *previous* value of the cell.
   Notice, by the way, that this technique can be extended to keep track of an
   arbitrary number of variables, if you create a rule that returns a tuple.
   We'll use this technique more later on.
   
   
 Beyond The Spreadsheet: "Receiver" Cells  Beyond The Spreadsheet: "Receiver" Cells
 ----------------------------------------  ----------------------------------------
   
Line 501 
Line 557 
 methods are being called in, and whether those methods are correctly updating  methods are being called in, and whether those methods are correctly updating
 everything they should.  everything they should.
   
 Thus, as long as a cell's rule doesn't raise an uncaught exception, there is no  Thus, as long as a cell's rule doesn't modify *anything* except local
 way for it to become "corrupt" or "out of sync" with the rest of the program.  variables, there is no way for it to become "corrupt" or "out of sync" with the
 This is a form of something called "referential transparency", which roughly  rest of the program.  This is a form of something called "referential
 means "order independent".  We'll cover this topic in more detail in the later  transparency", which roughly means "order independent".  We'll cover this topic
 section on `Managing State Changes`_.  But in the meantime, let's look at how  in more detail in the later section on `Managing State Changes`_.  But in the
 using receivers instead of methods also helps us implement generic controllers.  meantime, let's look at how using receivers instead of methods also helps us
   implement generic controllers.
   
   
 Creating Generic Controllers by Sharing Cells  Creating Generic Controllers by Sharing Cells
Line 585 
Line 642 
     >>> class TextEditor(trellis.Component):      >>> class TextEditor(trellis.Component):
     ...     text = trellis.value('')      ...     text = trellis.value('')
     ...      ...
     ...     @trellis.rule      ...     @trellis.action
     ...     def display(self):      ...     def display(self):
     ...         print "updating GUI to show", repr(self.text)      ...         print "updating GUI to show", repr(self.text)
   
Line 615 
Line 672 
 new high temperature is seen::  new high temperature is seen::
   
     >>> class HighDetector(trellis.Component):      >>> class HighDetector(trellis.Component):
     ...     max = None  
     ...     value = trellis.value(0)      ...     value = trellis.value(0)
       ...     max_and_new = trellis.value((None, False))
     ...      ...
     ...     @trellis.rule      ...     @trellis.rule
     ...     def new_high(self):      ...     def max_and_new(self):
     ...         if self.max is None:      ...         last_max, was_new = self.max_and_new
     ...             self.max = self.value      ...         if last_max is None:
     ...         elif self.value > self.max:      ...             return self.value, False    # first seen isn't a new high
     ...             self.max = self.value      ...         elif self.value > last_max:
     ...             return True      ...             return self.value, True
     ...         return False      ...         return last_max, False
     ...      ...
     ...     @trellis.rule      ...     trellis.rules(
       ...         new_high = lambda self: self.max_and_new[1]
       ...     )
       ...
       ...     @trellis.action
     ...     def monitor(self):      ...     def monitor(self):
     ...         if self.new_high:      ...         if self.new_high:
     ...             print "New high"      ...             print "New high"
   
   The ``max_and_new`` rule returns two values: the current maximum, and a flag
   indicating whether a new high was reached.  It refers to itself in order to
   see its own *previous* value, so it can tell whether a new high has been
   reached.  We set a default value of ``(None, False)`` so that the first time
   it's run, it will initialize itself correctly.  We then split out the "new
   high" flag from the tuple, using another rule.
   
   The reason we do the calculation this way, is that it makes our rule
   "re-entrant".  Because we're not modifying anything but local variables,
   it's impossible for an error in this rule to leave any corrupt data behind.
   We'll talk more about how (and why) to do things this way in the section below
   on `Managing State Changes`_.
   
   In the meantime, let's take our ``HighDetector`` for a test drive::
   
     >>> hd = HighDetector()      >>> hd = HighDetector()
   
     >>> hd.value = 7      >>> hd.value = 7
Line 644 
Line 720 
   
 Normal rules return what might be called "continuous" or "steady state" values.  Normal rules return what might be called "continuous" or "steady state" values.
 That is, their value remains the same until something causes them to be  That is, their value remains the same until something causes them to be
 recalculated.  In this case, the second recalculation returns ``True``, just  recalculated.  In this case, the second recalculation of ``new_high`` returns
 like the first one...  meaning that there's no change, and no observer  ``True``, just like the first one...  meaning that there's no change, and no
 recalculation.  observer recalculation.
   
 But "discrete" rules are different.  Just like receivers, their value is  But "discrete" rules are different.  Just like receivers, their value is
 automatically reset to a default value as soon as all their observers have  automatically reset to a default value as soon as all their observers have
Line 654 
Line 730 
   
     >>> class HighDetector2(HighDetector):      >>> class HighDetector2(HighDetector):
     ...     new_high = trellis.value(False) # <- the default value      ...     new_high = trellis.value(False) # <- the default value
     ...      ...     new_high = trellis.discrete(lambda self: self.max_and_new[1])
     ...     @trellis.discrete       # <- instead of trellis.rule  
     ...     def new_high(self):  
     ...         if self.max is None:  
     ...             self.max = self.value  
     ...         elif self.value > self.max:  
     ...             self.max = self.value  
     ...             return True  
     ...         return False  
   
     >>> hd = HighDetector2()      >>> hd = HighDetector2()
   
Line 678 
Line 746 
     New high      New high
   
 As you can see, each new high is detected correctly now, because the value  As you can see, each new high is detected correctly now, because the value
 of ``new_high`` resets to ``False`` after it's calculated as anything else::  of ``new_high`` resets to ``False`` after it's calculated as (or set to) any
   other value::
   
     >>> hd.new_high      >>> hd.new_high
     False      False
Line 835 
Line 904 
 * Input code, that sets trellis cells or calls modifier methods (but does not  * Input code, that sets trellis cells or calls modifier methods (but does not
   run inside trellis rules)    run inside trellis rules)
   
 * Processing rules that compute values, but do not directly change any other  * Processing rules that compute values, but do not make changes to any other
   cells    cells, attributes, or other data structures (apart from local variables)
   
 * Output code, that neither set values nor compute them, but simply sends data  * Action rules that send data on to other systems (like the screen, a socket,
   on to other systems (like the screen, a socket, a database, etc.).  This code    a database, etc.).  This code may appear in ``@trellis.action`` rules, or it
   may appear in standalone trellis rules, or it can be "application" code that    can be "application" code that reads results from a finished trellis
   reads results from a finished trellis calculation.    calculation.
   
 The first and third kinds of code are inherently order-dependent, since  The first and third kinds of code are inherently order-dependent, since
 information comes in (and must go out) in a meaningful order.  However, by  information comes in (and must go out) in a meaningful order.  However, by
 putting related outputs in the same output-only rule (or non-rule code), you  putting related outputs in the same action rule (or non-rule code), you can
 can ensure that the required order is enforced by a single piece of code.  This  ensure that the required order is enforced by a single piece of code.  This
 approach is highly bug-resistant.  approach is highly bug-resistant.
   
 Second, you can reduce the order dependency of input code by making it do as  Second, you can reduce the order dependency of input code by making it do as
Line 874 
Line 943 
 write, understand, and debug.  write, understand, and debug.
   
   
 Rule 1 - If Order Matters, Use Only One Rule  Rule 1 - If Order Matters, Use Only One Action
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
 If you care what order two "outside world" side-effects happen in, code them  If you care what order two "outside world" side-effects happen in, code them
 both in the same rule.  both in the same action rule.
   
 For example, in the ``TempConverter`` demo, we had a rule that printed the  For example, in the ``TempConverter`` demo, we had a rule that printed the
 Celsius and Fahrenheit temperatures.  If we'd put those two ``print``  Celsius and Fahrenheit temperatures.  If we'd put those two ``print``
 statements in separate rules, we'd have had no control over the output order;  statements in separate actions, we'd have had no control over the output order;
 either Celsius or Fahrenheit might have come first on any given change to the  either Celsius or Fahrenheit might have come first on any given change to the
 temperatures.  So, if you care about the relative order of certain output or  temperatures.  So, if you care about the relative order of certain output or
 actions, you must put them all in one rule.  If that makes the rule too big or  actions, you must put them all in one rule.  If that makes the rule too big or
 complex, you can always refactor to extract new rules to calculate the  complex, you can always refactor to extract new rules to calculate the
 intermediate values.  Just don't put any of the *actions* (i.e. side-effects or  intermediate values.  Just don't put any of the *actions* (i.e. side-effects or
 outputs) in the other rules, only the *calculations*.  Then have a rule that  outputs) in the other rules, only the *calculations*.  Then have an action rule
 *only* does the output or actions.  that *only* does the output or actions.
   
   
 Rule 2 - Return Values, Don't Set Them  Rule 2 - Return Values, Don't Set Them
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
 Except for output rules that are updating other systems, rule should always  Rules should always *compute* a value, rather than changing other values.  If
 *compute* a value, rather than changing other values.  If you need to compute  you need to compute more than one thing at once, just make a rule that returns
 more than one thing at once, just make a rule that returns a tuple or some  a tuple or some other data structure, then make other rules that pull the
 other data structure, then make other rules that pull the values out.  E.g.::  values out.  E.g.::
   
     >>> class Example(trellis.Component):      >>> class Example(trellis.Component):
     ...     trellis.rules(      ...     trellis.rules(
Line 912 
Line 981 
 system.  Remember: rules are not callbacks!  So always *return* values instead  system.  Remember: rules are not callbacks!  So always *return* values instead
 of *assigning* values.  of *assigning* values.
   
   If you need to keep track of some value between invocations of the same rule,
   make that value part of the rule's return value, then refer back to that value
   each time.  See the sections above on `Accessing a Rule's Previous Value`_ and
   `"Discrete" Rules`_ for examples of rules that re-use their previous value,
   and/or use a tuple to keep track of state.
   
   
 Rule 3 - If You MUST Set, Do It From One Place or With One Value  Rule 3 - If You MUST Set, Do It From One Place or With One Value
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
 If you set a value from more than one place, you are introducing an order  If you set a value from more than one place, you are introducing an order
 dependency.  In fact, if you set a value more than once in a rule or modifier,  dependency.  In fact, if you set a value more than once in an action or
 the Trellis will stop you.  After all, all changes in a rule or modifier happen  modifier, the Trellis will stop you.  After all, all changes in an action or
 "at the same time".  And what would it mean to set a value to 22 and 33 "at the  modifier happen "at the same time".  And what would it mean to set a value to
 same time"?  A conflict error, that's what it would mean::  22 and 33 "at the same time"?  A conflict error, that's what it would mean::
   
     >>> @trellis.modifier      >>> @trellis.modifier
     ... def set_twice():      ... def set_twice():
Line 955 
Line 1030 
 discards it.  discards it.
   
   
   Rule 4 - Change Takes Time
   ~~~~~~~~~~~~~~~~~~~~~~~~~~
   
   Be aware that if you ever change a cell or other Trellis data structure from
   inside an ``@action`` rule, this will trigger a recalculation of the trellis,
   once all current action rules are completed.  This effectively causes a loop,
   which *may not terminate* if your action rule is triggered again.  So beware of
   making such changes; there is nearly always a better way to get the result
   you're looking for -- i.e., one that doesn't involve action rules.
   
   
 Mutable Data Structures  Mutable Data Structures
 =======================  =======================
   
Line 1045 
Line 1131 
     >>> d.added      >>> d.added
     {}      {}
   
 Also note, however, that you cannot use the ``.pop()``, ``.popitem()``, or  Also note that you cannot use the ``.pop()``, ``.popitem()``, or
 ``.setdefault()`` methods of ``Dict`` objects::  ``.setdefault()`` methods of ``Dict`` objects::
   
     >>> d.setdefault(1, 2)      >>> d.setdefault(1, 2)
Line 1244 
Line 1330 
   
 * __cells__ attribute  * __cells__ attribute
   
 * Cell, Constant  * Cell, Constant, ActionCell
   
 * .link  * .link
   
Line 1272 
Line 1358 
 dirty()  dirty()
     Force the current rule's return value to be treated as if it changed      Force the current rule's return value to be treated as if it changed
   
 without_observer(func, \*args, \**kw)  
     Run func(\*args, \**kw) without making the current rule depend on it  
   
 .ensure_recalculation()  .ensure_recalculation()
     Ensure that this cell's rule will be (re)calculated      Ensure that this cell's rule will be (re)calculated
   


Generate output suitable for use with a patch program
Legend:
Removed from v.2379  
changed lines
  Added in v.2380

cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help