Bill Atkins <[EMAIL PROTECTED]> writes: > Alexander Schmolck <[EMAIL PROTECTED]> writes: > >> [trimmed groups] >> >> Ken Tilton <[EMAIL PROTECTED]> 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 -- http://mail.python.org/mailman/listinfo/python-list