A critic of Guido's blog on Python's lambda

Alexander Schmolck a.schmolck at gmail.com
Sun May 7 12:45:24 EDT 2006

[trimmed groups]

Ken Tilton <kentilton at gmail.com> writes:

> yes, but do not feel bad, everyone gets confused by the /analogy/ to
> spreadsheets into thinking Cells /is/ a spreadsheet. In fact, for a brief
> period I swore off the analogy because it was so invariably misunderstood.
> Even Graham misunderstood it.

Count me in.

> But it is such a great analogy! <sigh>
> > but what's the big deal about PyCells?
> > Here is 22-lines barebones implementation of spreadsheet in Python,
> > later I create 2 cells "a" and "b", "b" depends on a and evaluate all
> > the cells. The output is
> > a = negate(sin(pi/2)+one) = -2.0
> > b = negate(a)*10 = 20.0
> Very roughly speaking, that is supposed to be the code, not the output. So you
> would start with (just guessing at the Python, it has been years since I did
> half a port to Python):
>    v1 = one
>    a = determined_by(negate(sin(pi/2)+v1)
>    b = determined_by(negate(a)*10)
>    print(a) -> -2.0 ;; this and the next are easy
>    print(b) -> 20
>    v1 = two ;; fun part starts here
>    print(b) -> 40 ;; of course a got updated, too

do you mean 30?

I've translated my interpretation of the above to this actual python code:

from math import sin, pi
v1 = cell(lambda: 1)
a = cell(lambda:-(sin(pi/2)+v1.val), dependsOn=[v1])
b = cell(lambda: -a.val*10, dependsOn=[a], 
         onChange=lambda *args: printChangeBlurp(name='b',*args))
print 'v1 is', v1
print 'a is', a # -2.0 ;; this and the next are easy
print 'b is', b # 20
v1.val = 2 # ;; fun part starts here
print 'v1 now is', v1
print 'b now is', b # 30 ;; of course a got updated, too

I get the following printout:

v1 is 1
a is -2.0
b is [cell 'b' changed from <__main__.unbound object at 0xb4e2472c> to 20.0,
it was not bound]20.0
[cell 'b' changed from 20.0 to 30.0, it was bound ] v1 now is 2
b now is 30.0

Does that seem vaguely right?

> The other thing we want is (really inventing syntax here):
>    on_change(a,new,old,old-bound?) print(list(new, old, old-bound?)

Is the above what you want (you can also dynamically assign onChange later
on, as required or have a list of procedures instead)?

> Then the print statements Just Happen. ie, It is not as if we are just hiding
> computed variables behind syntax and computations get kicked off when a value
> is read. Instead, an underlying engine propagates any assignment throughout
> the dependency graph before the assignment returns.

Updating on write rather than recalculating on read does in itself not seem
particularly complicated.

> My Cells hack does the above, not with global variables, but with slots (data
> members?) of instances in the CL object system. I have thought about doing it
> with global variables such as a and b above, but never really seen much of
> need, maybe because I like OO and can always think of a class to create of
> which the value should be just one attribute.

OK, so in what way does the quick 35 line hack below also completely miss your

# (NB. for lispers: 'is' == EQ; '==' is sort of like EQUAL)

def printChangeBlurp(someCell, oldVal, newVal, bound, name=''):
    print '[cell %r changed from %r to %r, it was %s]' % (
        name, oldVal, newVal, ['not bound', 'bound '][bound]),

_unbound = type('unbound', (), {})() # just an unique dummy value
def updateDependents(dependents):
    seen = {}
    for dependent in dependents:
        if dependent not in seen:
            seen[dependent] = True
class cell(object):
    def __init__(self, formula, dependsOn=(), onChange=None):
        self.formula = formula
        self.dependencies = dependsOn
        self.onChange = onChange
        self._val = _unbound
        for dependency in self.dependencies:
            if self not in dependency._dependents:
        self._dependents = []
    def __str__(self):
        return str(self.val)
    def recalculate(self):
        newVal = self.formula()
        if self.onChange is not None:
            oldVal = self._val
            self.onChange(self, oldVal, newVal, oldVal is not _unbound)
        self._val = newVal
    def getVal(self):
        if self._val is _unbound:
        return self._val
    def setVal(self, value):
        self._val = value
    val = property(getVal, setVal)


More information about the Python-list mailing list