A critic of Guido's blog on Python's lambda
Bill Atkins
NOatkinwSPAM at rpi.edu
Sun May 7 14:34:14 EDT 2006
Bill Atkins <NOatkinwSPAM at rpi.edu> writes:
> Alexander Schmolck <a.schmolck at gmail.com> writes:
>
>> [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
>> point?
>>
>>
>> # (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
>> dependent.recalculate()
>> updateDependents(dependent._dependents)
>> 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:
>> dependency._dependents.append(self)
>> 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:
>> self.recalculate()
>> return self._val
>> def setVal(self, value):
>> self._val = value
>> updateDependents(self._dependents)
>> val = property(getVal, setVal)
>>
>>
>>
>> 'as
>
> Here's how one of the cells examples might look in corrupted Python
> (this is definitely not executable):
>
> class FallingRock:
> def __init__(self, pos):
> define_slot( 'velocity', lambda: self.accel * self.elapsed )
> define_slot( 'pos', lambda: self.accel * (self.elapsed ** 2) / 2,
> initial_position = cell_initial_value( 100 ) )
> self.accel = -9.8
>
> rock = FallingRock(100)
> print rock.accel, rock.velocity, rock.pos
> # -9.8, 0, 100
>
> rock.elapsed = 1
> print rock.accel, rock.velocity, rock.pos
> # -9.8, -9.8, -9.8
>
> rock.elapsed = 8
> print rock.accel, rock.velocity, rock.pos
> # -9.8, -78.4, -627.2
>
> Make sense? The idea is to declare what a slot's value represents
> (with code) and then to stop worrying about keeping different things
> synchronized.
>
> Here's another of the examples, also translated into my horrific
> rendition of Python (forgive me):
>
> class Menu:
> def __init__(self):
> define_slot( 'enabled',
> lambda: focused_object( self ).__class__ == TextEntry and
> focused_object( self ).selection )
>
> Now whenever the enabled slot is accessed, it will be calculated based
> on what object has the focus. Again, it frees the programmer from
> having to keep these different dependencies updated.
>
> --
> This is a song that took me ten years to live and two years to write.
> - Bob Dylan
Oh dear, there were a few typos:
class FallingRock:
def __init__(self, pos):
define_slot( 'velocity', lambda: self.accel * self.elapsed )
define_slot( 'pos', lambda: self.accel * (self.elapsed ** 2) / 2,
initial_value = cell_initial_value( 100 ) )
self.accel = -9.8
rock = FallingRock(100)
print rock.accel, rock.velocity, rock.pos
# -9.8, 0, 100
rock.elapsed = 1
print rock.accel, rock.velocity, rock.pos
# -9.8, -9.8, 90.2
rock.elapsed = 8
print rock.accel, rock.velocity, rock.pos
# -9.8, -78.4, -527.2
--
This is a song that took me ten years to live and two years to write.
- Bob Dylan
More information about the Python-list
mailing list