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