dynamically updating dependency networks in Smalltalk
Kragen Sitaker
kragen at pobox.com
Tue Jun 22 15:13:54 EDT 2004
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).! !
More information about the Kragen-hacks
mailing list