[PEAK] Lessons from the new API
Phillip J. Eby
pje at telecommunity.com
Mon Apr 14 20:37:14 EDT 2008
So, I've been doing a bit of work with the new Trellis API in
SVN. So far, the thing I like best about it is that it removes
"spooky action at a distance" -- having to fully define an attribute
in one place improves readability.
I'm not too thrilled with constructs like
'trellis.variable.attributes', which seems terribly redundant. I'm
thinking about changing 'variable' to 'attribute' or even 'attr', as
it's used quite often.
But the worst part of the new API so far is that the distinction
between 'compute' and 'maintain' isn't crystal clear to me. In
particular, 'compute' has two very different use cases:
1. Initializing the value of an attribute that will then either be
writable or constant, and
2. Performing a calculation that has no visible side-effects (and can
thus be skipped or repeated with impunity).
The first use case doesn't really fit the name 'compute', and it
makes it harder to immediately identify what descriptor should be
used for a given rule.
After some discussion with Grant on the #chandler IRC channel, it has
occurred to me that for use case #1 we want something more like an
old-school PEAK 'binding.Make'. A Make descriptor could take a type,
a zero, one, or 2-argument callable, or even a string designating
something to be imported later, and use it to initialize the
attribute, such that e.g.:
foo = binding.Make(dict)
would result in you having an empty dictionary when you access the
foo attribute for the first time. You could also pass it a lambda,
or any number of other things.
Anyway, most of the "initialization" use case for 'compute' could be
met by adding a 'make' keyword to 'attr' (fka 'variable' fka 'value')
so that e.g.:
foo = trellis.attr(make=dict)
Would produce a writable attribute, initialized to an empty
dictionary. It would also be a nice bonus if you could say:
@trellis.maintain(make=dict)
def foo(self):
...
in order to avoid the common tedium of writing:
@trellis.maintain
def foo(self):
foo = self.foo
if foo is None:
foo = {}
...
What is lost with using this syntax in place of 'compute' for the
initialization use case, is that 'compute' can currently be used to
dynamically create a Constant -- an attribute value that will not
change for the life of the object instance.
This is tricky, because the *default* state under the old API was
that rules do not produce writable cells unless you explicitly make
them writable. This is a performance booster in the case where a
cell can become constant dynamically.
Anyway, the point is that in most cases where we'd use
'attr(make=...)', the result would have been a constant under the old API.
Now, the only benefit of a constant over a regular cell is memory
usage. Depending on a constant doesn't create a Link object tracking
the dependency. Apart from that, there's no other overhead. So,
maybe it doesn't matter that much. When I first implemented
constant-izing, it was in imitation of Kenny Tilton's "Cells" package
for Lisp, which had dependency-checking overhead for each cell used
in a rule. However, for some time now the Trellis hasn't used an
algorithm with that overhead. So, maybe there's no real point in
worrying about it any more.
So, I guess this is my current thinking on how the current interim
API should change:
variable -> attr
variable.attributes -> attrs
variable.attributes.resetting_to -> resetting_attrs
compute.attributes -> compute.attrs
maintain.attributes -> maintain.attrs
* add a 'make' keyword to 'maintain()'
* add a 'make()' attribute type, to allow you to create optional
computed values, that can be declared writable
* add a 'make.attrs()' callable, to allow creating multiple make() attributes
In all cases the behavior of these 'make' targets would be such that
passing in a type or a zero-argument callable would just call it,
while single-argument functions will receive a self parameter, and so
on -- much like PEAK's 'binding.Make()'.
This increases the vocabulary size a bit, but I think it clarifies a
lot of things. Many times, I find myself using trellis attributes
simply to conveniently make something, and it should probably be
distinguished better from repeatable (compute) and required
non-repeatable (maintain) calculations.
I am somewhat tempted to also shorten 'compute' to 'calc' and
'maintain' to 'ensure'. But that's probably pushing it. On the
other hand, 'calc' seems a bit more like it means "side-effect
free". So I'm open to feedback on that, or other suggestions for how
to name the central words.
Comments welcome.
More information about the PEAK
mailing list