This lets you construct DAGs of arbitrary values computed by arbitrary blocks, and efficiently updates all outdated values whenever any of them change. There's a handy #value: method to set the value to a particular constant.
Squeak (well, Smalltalk) is a pretty pleasant environment for this kind of thing. In particular the support for circular pointers (at first, before I used WeakSet) and WeakSet were nice here, nicer than Python; being able to refer to '+' as an ordinary method was nice (much nicer than the equivalent in Python); and #detect:ifNone: seems nicer to me than the equivalent locutions in other languages. Consider: parents detect: [ :p | p valid not ] ifNone: [ foo ]. versus if not filter(lambda p: not p.valid(), parents): foo if not [p for p in parents if not p.valid()]: foo foo unless grep { not $_->valid } @parents; Testing to ensure that the WeakSet really was weak was a little harder than in Python, in the sense that I had to explicitly issue a "Smalltalk garbageCollectMost", but I could see the results immediately in an inspector window, which made up for it. I seem to recall that there's some sort of observer-observed pattern already built into Smalltalk but I couldn't find it. 'From Squeak3.6 of ''6 October 2003'' [latest update: #5429] on 22 June 2004 at 6:13:57 am'! Object subclass: #DynUpdater instanceVariableNames: 'value valid children formula parents ' classVariableNames: '' poolDictionaries: '' category: 'My stuff'! !DynUpdater commentStamp: 'kjs 6/22/2004 06:01' prior: 0! Dynamically updating numbers (or whatever) in formula networks. "((DynUpdater withValue: 1) + (DynUpdater withValue: 2)) value" Structure: valid Boolean -- whether I currently have a value value Object -- current value (if valid) children WeakSet -- the other variables whose values depend on me formula Block -- how my value is computed from my parents parents OrderedCollection -- the variables from whom I compute my value. The 'valid' bit is used to keep a single input value change from causing an exponential number of recalculations.! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 05:23'! + anOther "Answer a dynamically updating variable for my sum with another." ^ self withBinop: #+ andOther: anOther.! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:01'! addChild: aDynUpdater "Add a variable that depends on me." children add: aDynUpdater! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:02'! formula: newFormula parents: newParents "Set my formula and parents, initializing me if necessary." valid ifNil: [ valid _ false. children _ WeakSet new. parents _ #(). ]. self invalidate. parents do: [ :p | p removeChild: self ]. parents _ newParents. parents do: [ :p | p addChild: self ]. formula _ newFormula. self recalculate.! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:02'! invalidate "Calling this tells me that my current value is invalid." valid ifTrue: [ valid _ false. value _ nil. children do: [ :d | d invalidate]. ].! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:12'! newValue: newValue "Called internally to set the new value." value _ newValue. valid _ true. children do: [ :d | d recalculate ]. ! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:11'! recalculate "Calling this tells me one of my parents has become valid." formula ifNil: [ self error: 'Can''t recalculate without a formula.' ]. valid ifTrue: [^self]. "I already know my value." parents detect: [ :p | p valid not ] ifNone: [ self newValue: (formula valueWithArguments: (parents collect: [ :p | p value ])). ].! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 00:24'! removeChild: aDynUpdater children remove: aDynUpdater! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 03:57'! valid ^valid.! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 03:57'! value valid ifFalse: [self error: 'Can''t read value; presently invalid']. ^value.! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:03'! value: newValue "Make me merely return a constant value." self formula: [newValue] parents: #(). ! ! !DynUpdater methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:09'! withBinop: aBinop andOther: anOther ^ self class withFormula: [ :a :b | a value perform: aBinop with: b value ] andParents: { self. anOther }. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DynUpdater class instanceVariableNames: ''! !DynUpdater class methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:05'! withFormula: aFormula andParents: newParents "Answer a DynUpdater depending on newParents computing its value with aFormula." ^ (DynUpdater new formula: aFormula parents: newParents)! ! !DynUpdater class methodsFor: 'as yet unclassified' stamp: 'kjs 6/22/2004 06:04'! withValue: aValue "Answer a DynUpdater that will return value aValue." ^ (DynUpdater new value: aValue).! !