A trellis.Component is an object that can have its attributes automatically maintained by rules, the way a spreadsheet is maintained by its formulas.

These managed attributes are called "cell attributes", because the attribute values are stored in "cell" ( trellis.AbstractCell ) objects. Cell objects can be variable or constant, and either computed by a rule or explicitly set to a value -- possibly both, as in the temperature converter example!

There are five basic types of cell attributes:

Passive, Settable Values ( attr() and attrs() ) These are simple read-write attributes, with a specified default value. Rules that read these values will be automatically recalculated after the attribute is changed. Computed Constants Or Initialized Values ( make() and make.attrs() ) These attributes are usually used to hold a mutable object, such as a list or dictionary (e.g. cache = trellis.make(dict) ). The callable (passed in when you define the attribute) will be called at most once for each instance, in order to initialize the attribute's value. After that, the same object will be returned each time. (Unless you make the attribute writable, and set the attribute to a new value.) Computed, Observable Values ( @compute and compute.attrs() ) These attributes are used to compute simple formulas, much like those in a spreadsheet. That is, ones that calculate a current state based on the current state of other values. Formulas used in @compute attributes must be non-circular, side-effect free, and cannot depend on the attribute's previous value. They are automatically recalculated when their dependencies change, but only if a maintenance or action-performing rule depends upon the result, either directly or indirectly. (This avoids unnecessary recalculation of values that nobody cares about.) Maintenance Rules/Maintained Values ( @maintain and maintain.attrs() ) These rules or attribute values are used to reflect changes in state. A maintenance rule can modify other values or use its own previous value in a calculation. It is re-invoked any time a value it has previously used changes, even if no other rule depends upon it. Maintenance rules can be circular, as in the temperature converter example, as their values can be explicitly set -- both as an initial value, and at runtime. They are also used to implement "push" or "pull" rules that update one data structure in response to changes made in another data structure. All side-effects in maintenance rules must be undo-able using the Trellis's undo API. (Which is automatic if the side-effects happen only on trellis attributes or data structures.) But if you must change non-trellis data structures inside a maintenance rule, you will need to log undo actions. We'll discuss the undo log mechanism in more detail later, in the section on Creating Your Own Data Structures. Action-Performing Rules ( @perform ) These rules are used to perform non-undoable actions on non-trellis data or systems, such as output I/O and calls to other libraries. Like maintenance rules, they are automatically re-invoked whenever a value they've previously read has changed. Unlike maintenance rules, however, they cannot return a value or modify any trellis data. Note, by the way, that this means performing rules should never raise errors. If they do, the changes that caused the rule to run will be rolled back, but if any other performing rules were run first, their actions will not be rolled back, leaving your application in an inconsistent state.

For each of the attribute types, you can use the plural attrs() form (if there is one) to define multiple attributes at once in the body of a class. The singular forms (except for attr() ) can be used either inline or as function decorators wrapping a method to be used as the attribute's rule.

Let's take a look at a sample class that uses some of these ways to define different attributes, being deliberately inconsistent just to highlight some of the possible options:

>>> class Rectangle(trellis.Component): ... trellis.attrs( ... top = 0, ... width = 20, ... ) ... left = trellis.attr(0) ... height = trellis.attr(30) ... ... trellis.compute.attrs( ... bottom = lambda self: self.top + self.height, ... ) ... ... @trellis.compute ... def right(self): ... return self.left + self.width ... ... @trellis.perform ... def show(self): ... print self ... ... def __repr__(self): ... return "Rectangle"+repr( ... ((self.left,self.top), (self.width,self.height), ... (self.right,self.bottom)) ... ) >>> r = Rectangle(width=40, height=10) Rectangle((0, 0), (40, 10), (40, 10)) >>> r.width = 17 Rectangle((0, 0), (17, 10), (17, 10)) >>> r.left = 25 Rectangle((25, 0), (17, 10), (42, 10))

By the way, note that computed attributes (as well as make attributes by default) will be read-only:

>>> r.bottom = 99 Traceback (most recent call last): ... AttributeError: can't set attribute

However, "maintained" attributes will be writable if you supply an initial value, as we did in the TemperatureConverter example. (Plain attr attributes are always writable, and make attributes can be made writable by passing in writable=True when creating them.)

Note, by the way, that you aren't required to make everything in your program a trellis.Component in order to use the Trellis. The Component class does only four things, and you are free to accomplish these things some other way if you need or want to:

It sets self.__cells__ = trellis.Cells(self) . This creates a special dictionary that will hold all the Cell objects used to implement cell attributes. The __init__ method takes any keyword arguments it receives, and uses them to initialize any named attributes. (Note that this is the only thing the __init__ method does, so you don't have to call it unless you want this behavior.) 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 cover this in more detail in the next section, Automatic Activation and Dependencies. It wraps the entire object creation process in a @modifier , so that all of the above operations occur in a single logical transaction. We'll cover this more in a later section on Managing State Changes.

In addition to doing these things another way, you can also use Cell objects directly, without any Component classes. This is discussed more in the section below on Working With Cell Objects.

Automatic Activation and Dependencies You'll notice that each time we change an attribute value, our Rectangle instance above prints itself -- including when the instance is first created. That's because of two important Trellis principles: When a Component instance is created, all its "non-optional" cell attributes are calculated after initialization is finished. That is, if the attribute is a maintenance or performing rule, and has not been marked optional, then the rule is invoked, and the result is used to determine the cell's initial value. While a cell's rule is running, any trellis cell whose value is looked at becomes a dependency of that rule. If the looked-at cell changes later, it triggers recalculation of the rule that "looked". In Trellis terms, we say that the first cell has become a "listener" of the second cell. The first of these principles explains why the rectangle printed itself immediately: the show performer cell was activated. We can see this if we look at the rectangle's show attribute: >>> print r.show None (The show rule is a performer, so the resulting attribute value is None . Also notice that rules are not methods -- they are more like properties.) The second principle explains why the rectangle re-prints itself any time one of the attributes changes value: all six attributes are referenced by the __repr__ method, which is called when the show performer prints the rectangle. Since the cells that store those attributes are being looked at during the execution of another cell's rule, they become dependencies, and the show rule is thus re-run whenever the listened-to cells change. Each time a rule runs, its dependencies are automatically re-calculated -- which means that if you have more complex rules, they can actually depend on different cells every time they're calculated. That way, the rule is only re-run when it's absolutely necessary. By the way, a listened-to cell has to actually change its value (as determined by the != operator), in order to trigger recalculation. Merely setting a cell doesn't cause its observers to recalculate: >>> r.width = 17 # doesn't trigger ``show`` But changing it to a non-equal value does: >>> r.width = 18 Rectangle((25, 0), (18, 10), (43, 10))

"Optional" Rules and Subclassing 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 turn it into an "optional" performer, so that it won't run unless we ask it to: >>> class QuietRectangle(Rectangle): ... @trellis.perform(optional=True) ... def show(self): ... print self By subclassing Rectangle , we inherit all of its cell attribute definitions. We call our new optional rule show so that its definition overrides the noisy version of the rule. And, because it's marked optional, it isn't automatically activated when the instance is created. So we don't get any announcements when we create an instance or change its values: >>> q = QuietRectangle(width=18, left=25) >>> q.width = 17 Unless, of course, we activate the show rule ourselves: >>> q.show Rectangle((25, 0), (17, 30), (42, 30)) And from now on, it'll be just as chatty as the previous rectangle object: >>> q.left = 0 Rectangle((0, 0), (17, 30), (17, 30)) While any other QuietRectangle objects we create will of course remain silent, since we haven't activated their show cells: >>> q2 = QuietRectangle() >>> q2.top = 99 @compute rules are always "optional". make() attributes are optional by default, but can be made non-optional by passing in optional=False . @maintain and @perform are non-optional by default, but can be made optional using optional=True . Notice, by the way, that rule attributes are more like properties than methods, which means you can't use super() to call the inherited version of a rule. (Later, we'll look at other ways to access rule definitions.)

Read-Only and Read-Write Attributes Attributes can vary as to whether they're settable: Passive values ( attr() , attrs() ) and @maintain rules are always settable

, ) and rules are always settable make() attributes are settable only if created with writable=True

attributes are settable only if created with @compute and @perform attributes are never settable For example, here's a class with a non-settable aDict attribute: >>> class Demo(trellis.Component): ... aDict = trellis.make(dict) >>> d = Demo() >>> d.aDict {} >>> d.aDict[1] = 2 >>> d.aDict {1: 2} >>> d.aDict = {} Traceback (most recent call last): ... AttributeError: Constants can't be changed Note, however, that even if an attribute isn't settable, you can still initialize the attribute value, before the attribute's cell is created: >>> d = Demo(aDict={3:4}) >>> d.aDict {3: 4} >>> d = Demo() >>> d.aDict = {1:2} >>> d.aDict {1: 2} Since the aDict attribute is "optional" ( make attributes are optional by default), it wasn't initialized when the Demo instance was created. So we were able to set an alternate initialization value. But, if we make it non-optional, we can't do this, because the attribute will be initialized during instance construction: >>> class Demo(trellis.Component): ... aDict = trellis.make(dict, optional=False) >>> d = Demo() >>> d.aDict = {1:2} Traceback (most recent call last): ... AttributeError: Constants can't be changed And so, non-optional read-only attributes can only be set while an instance is being created: >>> d = Demo(aDict={3:4}) >>> d.aDict {3: 4} But if an attribute is settable, it can be set at any time, whether the attribute is optional or not: >>> class Demo(trellis.Component): ... aDict = trellis.make(dict, writable=True) >>> d = Demo() >>> d.aDict = {1:2} >>> d.aDict = {3:4}

Model-View-Controller and the "Observer" Pattern As you can imagine, the ability to create rules like this can come in handy for debugging. Heck, there's no reason you have to print the values, either. If you're making a GUI application, you can define rules that update displayed fields to match application object values. For that matter, you don't even need to define the rule in the same class! For example: >>> class Viewer(trellis.Component): ... model = trellis.attr(None) ... ... @trellis.perform ... def view_it(self): ... if self.model is not None: ... print self.model >>> view = Viewer(model=q2) Rectangle((0, 99), (20, 30), (20, 129)) Now, any time we change q2, it will be printed by the Viewer's view_it rule, even though we haven't activated q2's show rule: >>> q2.left = 66 Rectangle((66, 99), (20, 30), (86, 129)) This means that we can automatically update a GUI (or whatever else might need updating), without adding any code to the thing we want to "observe". Just use cell attributes, and everything can use the "observer pattern" or be a "Model-View-Controller" architecture. Just define rules that can read from the "model", and they'll automatically be invoked when there are any changes to "view". Notice, by the way, that our Viewer object can be repointed to any object we want. For example: >>> q3 = QuietRectangle() >>> view.model = q3 Rectangle((0, 0), (20, 30), (20, 30)) >>> q2.width = 59 # it's not watching us any more, so no output >>> view.model = q2 # watching q2 again Rectangle((66, 99), (59, 30), (125, 129)) >>> q3.top = 77 # but we're not watching q3 any more See how each time we change the model attribute, the view_it rule is recalculated? The rule references self.model , which is a value cell attribute. So if you change view.model , this triggers a recalculation, too. Remember: once a rule reads another cell, it will be recalculated whenever the previously-read value changes. Each time view_it is invoked, it renews its dependency on self.model , but also acquires new dependencies on whatever the repr() of self.model looks at. Meanwhile, any dependencies on the attributes of the previous self.model are dropped, so changing them doesn't cause the perform rule to be re-invoked any more. This means we can even do things like set model to a non-component object, like this: >>> view.model = {} {} But since dictionaries don't use any cells, changing the dictionary won't do anything: >>> view.model[1] = 2 To be able to observe mutable data structures, you need to use data types like trellis.Dict and trellis.List instead of the built-in Python types. We'll cover how that works in the section below on Mutable Data Structures. By the way, the links from a cell to its listeners are defined using weak references. This means that views (and cells or components in general) can be garbage collected even if they have dependencies. For more information about how Trellis objects are garbage collected, see the later section on Garbage Collection.

Accessing a Rule's Previous Value Sometimes it's useful to create a maintained value that's 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 a @maintain rule that refers to its previous value: >>> class NoiseFilter(trellis.Component): ... trellis.attrs( ... value = 0, ... threshhold = 5, ... ) ... @trellis.maintain(initially=0) ... 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. (Note: this is only possible in @maintain rules.)

Beyond The Spreadsheet: "Resetting" Cells So far, all the stuff we've been doing isn't really any different than what you can do with a spreadsheet, except maybe in degree. Spreadsheets usually don't allow the sort of circular calculations we've been doing, but that's not really too big of a leap. But practical programs often need to do more than just reflect the values of things. They need to do things, too. So far, we've seen only attributes that reflect a current "state" of things. But attributes can also represent things that are "happening", by automatically resetting to some sort of null or default value. In this way, you can use an attribute's value as a trigger to cause some action, following which it resets to an "empty" or "inactive" value. And this can then help us handle the "Controller" part of "Model-View-Controller". For example, suppose we want to have a controller that lets you change the size of a rectangle. We can use "resetting" attributes to do this, in a way similar to an "event", "message", or "command" in a GUI or other event-driven system: >>> class ChangeableRectangle(QuietRectangle): ... trellis.attrs.resetting_to( ... wider = 0, ... narrower = 0, ... taller = 0, ... shorter = 0 ... ) ... width = trellis.maintain( ... lambda self: self.width + self.wider - self.narrower, ... initially = 20 ... ) ... height = trellis.maintain( ... lambda self: self.height + self.taller - self.shorter, ... initially = 30 ... ) >>> c = ChangeableRectangle() >>> view.model = c Rectangle((0, 0), (20, 30), (20, 30)) A resetting attribute (created with attr(resetting_to=value) or attrs.resetting_to() ) works by receiving an input value, and then automatically resetting to its default value after its dependencies are updated. For example: >>> c.wider 0 >>> c.wider = 1 Rectangle((0, 0), (21, 30), (21, 30)) >>> c.wider 0 >>> c.wider = 1 Rectangle((0, 0), (22, 30), (22, 30)) Notice that setting c.wider = 1 updated the rectangle as expected, but as soon as all updates were finished, the attribute reset to its default value of zero. In this way, every time you put a value into a resetting attribute, it gets processed and discarded. And each time you set it to a non-default value, it's treated as a change. Which means that any maintenance or performing rules that depends on the attribute will be recalculated (along with any @compute rules in between). If we'd used a normal trellis.attr here, and then set c.wider = 1 twice in a row, nothing would have happen the second time, because the value would not have changed. Now, we could write methods for changing value cells that would do this sort of resetting for us, but it wouldn't be a good idea. We'd need to have both the attribute and the method, and we'd need to remember to never set the attribute directly. (What's more, it wouldn't even work correctly, for reasons we'll see later.) It's much easier to just use a resetting attribute as an "event sink" -- that is, to receive, consume, and dispose of any messages or commands you want to send to an object. But why do we need such a thing at all? Why not just write code that directly manipulates the model's width and height? Well, sometimes you can, but it limits your ability to create generic views and controllers, makes it impossible to "subscribe" to an event from multiple places, and increases the likelihood that your program will have bugs -- especially order-dependency bugs. If you use rules to compute values instead of writing code to manipulate values, then all the code that affects a value is in exactly one place. This makes it very easy to verify whether that code is correct, because the way the value is arrived at doesn't depend on what order a bunch of manipulation methods are being called in, and whether those methods are correctly updating everything they should. Thus, as long as a cell's rule doesn't modify anything except local variables, there is no way for it to become "corrupt" or "out of sync" with the rest of the program. This is a form of something called "referential transparency", which roughly means "order independent". We'll cover this topic in more detail in the later section on Managing State Changes. But in the meantime, let's look at how using attributes instead of methods also helps us implement generic controllers.

Creating Generic Controllers by Sharing Cells Let's create a couple of generic "Spinner" controllers, that take a pair of "increase" and "decrease" command attributes, and hook them up to our changeable rectangle: >>> class Spinner(trellis.Component): ... """Increase or decrease a value""" ... increase = trellis.attr(resetting_to=0) ... decrease = trellis.attr(resetting_to=0) ... by = trellis.attr(1) ... ... def up(self): ... self.increase = self.by ... ... def down(self): ... self.decrease = self.by >>> cells = trellis.Cells(c) >>> width = Spinner(increase=cells['wider'], decrease=cells['narrower']) >>> height = Spinner(increase=cells['taller'], decrease=cells['shorter']) The trellis.Cells() API returns a dictionary containing all active cells for the object. (We'll cover more about this in the section below on Working With Cell Objects_ .) You can then access them directly, assigning them to other components' attributes. Assigning a Cell object to a cell attribute allows two components to share the same cell. In this case, that means setting the .increase and .decrease attributes of our Spinner objects will set the corresponding attributes on the rectangle object, too: >>> width.up() Rectangle((0, 0), (23, 30), (23, 30)) >>> width.down() Rectangle((0, 0), (22, 30), (22, 30)) >>> height.by = 5 >>> height.down() Rectangle((0, 0), (22, 25), (22, 25)) >>> height.up() Rectangle((0, 0), (22, 30), (22, 30)) Could you do the same thing with methods? Maybe. But can methods be linked the other way?: >>> width2 = Spinner() >>> height2 = Spinner() >>> controlled_rectangle = ChangeableRectangle( ... wider = trellis.Cells(width2)['increase'], ... narrower = trellis.Cells(width2)['decrease'], ... taller = trellis.Cells(height2)['increase'], ... shorter = trellis.Cells(height2)['decrease'], ... ) >>> view.model = controlled_rectangle Rectangle((0, 0), (20, 30), (20, 30)) >>> height2.by = 10 >>> height2.up() Rectangle((0, 0), (20, 40), (20, 40)) A shared cell is a shared cell: it doesn't matter which "direction" you share it in! It's a simple way to create an automatic link between two parts of your program, usually between a view or controller and a model. For example, if you create a text editing widget for a GUI application, you can define a value cell for the text in its class: >>> class TextEditor(trellis.Component): ... text = trellis.attr('') ... ... @trellis.perform ... def display(self): ... print "updating GUI to show", repr(self.text) >>> te = TextEditor() updating GUI to show '' >>> te.text = 'blah' updating GUI to show 'blah' And then you'd write some additional code to automatically set self.text when there's accepted input from the GUI. An instance of this editor can then either maintain its own text cell, or be given a cell from an object whose attributes are being edited. This allows you to independently test your models, views, and controllers, then simply link them together at runtime in any way that's useful.

Resetting Rules Resetting attributes are designed to "accept" what might be called events, messages, or commands. But what if you want to generate or transform such events instead? Let's look at an example. Suppose you'd like to trigger an action whenever a new high temperature is seen: >>> class HighDetector(trellis.Component): ... value = trellis.attr(0) ... last_max = trellis.attr(None) ... ... @trellis.maintain ... def new_high(self): ... last_max = self.last_max ... if last_max is None: ... self.last_max = self.value ... return False # first seen isn't a new high ... elif self.value > last_max: ... self.last_max = self.value ... return True ... return False ... ... @trellis.perform ... def monitor(self): ... if self.new_high: ... print "New high" The new_high rule runs whenever value changes, and checks to see if it's greater than the current highest value. If so, it returns true and updates the maximum value. Let's try it out: >>> hd = HighDetector() >>> hd.value = 7 New high >>> hd.value = 9 Oops! We set a new high value, but the monitor rule didn't detect a new high, because new_high was already True from the previous high. Just as with a regular attribute, rules normally return what might be called "continuous" or "steady state" values. That is, their value remains the same until something causes them to be recalculated. In this case, the second recalculation of new_high returns True , just like the first one... meaning that there's no change, and thus the performing rule isn't triggered. But, just as with regular attributes, @compute and @maintain rules can be made "resetting", using the resetting_to= keyword, allowing the value to reset to a default as soon as all of the value's listeners have "seen" the original value. Let's try a new version of our high detector: >>> class HighDetector2(HighDetector): ... ... @trellis.maintain(resetting_to=False) ... def new_high(self): ... # this is a bit like a super() call, but for a rule: ... return HighDetector.new_high.rule(self) >>> hd = HighDetector2() >>> hd.value = 7 New high >>> hd.value = 9 New high >>> hd.value = 3 >>> hd.value = 16 New high As you can see, each new high is detected correctly now, because the value of new_high is silently reset to False after it's calculated as (or set to) any other value: >>> hd.new_high False >>> hd.new_high = True New high >>> hd.new_high False (By the way, that HighDetector.new_high.rule in the new new_high rule retrieves the base class version of the rule. We could also have done the same thing this way: >>> class HighDetector2(HighDetector): ... new_high = trellis.maintain( ... HighDetector.new_high.rule, resetting_to = False ... ) and the result would have been the same, except it would run faster since the lookup of the inherited rule only happens once.)