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

Bill Atkins NOatkinwSPAM at rpi.edu
Sun May 7 14:30:26 EDT 2006


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



More information about the Python-list mailing list