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

Bill Atkins NOatkinwSPAM at rpi.edu
Sun May 7 20:34:14 CEST 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