About KCP and automatic initialize

Andreas Raab andreas.raab at gmx.de
Fri Sep 12 00:52:47 UTC 2003


> It really has nothing to do with efficiency.
> It's about good design.

But then your example seems off. You are using the terms "correct" (as in:
correct for the year 1994) and "usable" (as in: adhering to the class
invariants) interchangeably. If this is what you expect from your TaxRule
(e.g., the class invariant being that it is correct for 1994) then you
should make a class named TaxRule1994 in which case the usable and correct
behavior can be achieved by #initializing it to the rules which held in 1994
(and it would be good design too).

In the absence of this goal of "client specific correctness" the class
invariants are all you can really shoot for. And as long as these are
preserved (either by initializing TaxRule to 1994, to Date today year, or to
some arbitrary identity/null mapping) it largely doesn't matter how exactly
you initialize an object - you will get back an object which adheres to
class invariants which is the only thing you can (and should) rely on if you
have a general TaxRule and not a specific one like TaxRule1994.

Cheers,
  - Andreas

> -----Original Message-----
> From: squeak-dev-bounces at lists.squeakfoundation.org 
> [mailto:squeak-dev-bounces at lists.squeakfoundation.org] On 
> Behalf Of Richard A. O'Keefe
> Sent: Friday, September 12, 2003 1:40 AM
> To: squeak-dev at lists.squeakfoundation.org
> Subject: Re: About KCP and automatic initialize
> 
> 
> ducasse <ducasse at iam.unibe.ch> continues the super new 
> initialize thread.
> By the way, I searched the Squeak mail archive, and I see 
> that this was
> being discussed back in 1999 (and by some of the same people...).
> 
> 	But still I'm puzzled. Why would you need that to avoid 
> one call to 
> 	initialize that is empty????
> 	Really one call in Squeak. Eliot says that in VW the 
> costs would be 
> 	completely invisible because of the
> 	iniline cache, the benchmark that noury sent prove that 
> without any 
> 	tricks we can really good
> 	result.
> 	
> It really has nothing to do with efficiency.
> It's about good design.
> Making the system call #initialize automatically is a way of
> encouraging people to *design* for #initialize, and the more
> Smalltalk code I look at, the more that seems to me to lead
> to bad design.
> 
> If I were making such radical changes to Squeak,
> I would probably ban zero-argument #initialize entirely.
> 
> Let's consider just one example, from a tutorial on the Swiki.
> I'm experimenting with a style for displaying classes which is
> a bit more readable than change-sets.
> 
>   Object subclass: #TaxRule
>     instanceVariableNames: 'breakPoints rates'
>     comment:
>       'I represent a tax table.
>        Given a yearly taxable income, I can give the tax rate for it.
>        I store the tax table as a sequence of amounts,
>        and the tax rate for that amount.
>        Example: in 1994 the tax rate for a single person 
> (Schedule X) was
> 	$     0 15%
> 	$ 22750 28%
> 	$ 55100 31%
> 	$115000 36%
> 	$250000 39.6%
>       '
>     category: 'initialize-release' methods:
>       initialize
>         breakPoints := SortedSequence new.
>         rates := OrderedCollection new.
> 
>     category: 'accessing' methods:
>       breakPoint: aNumber rate: anotherNumber
>         "Add an entry to the tax table"
>         |index|
>         index := breakPoints 
> indexOfStartOfIntervalContaining: aNumber.
>         breakPoints add: aNumber.
>         rates add: anotherNumber beforeIndex: index + 1.
>       rateFor: aNumber
>         "Return the tax rate for someone whose taxable income is
>          aNumber dollars."
> 	^rates at: (breakPoints 
> indexOfStartofIntervalContaining: aNumber)
> 
>     class category: 'instance creation' methods:
>       new
>         ^super new initialize
> 
> 
> Now, this is just such a class as would be simplified by the proposed
> change to Object.  If Object new sent #initialize, there 
> would be no need
> at all for #new in this class.
> 
> But there's something rather upsetting about this class.
> The object returned by #new IS NOT IN FACT USABLE, despite having
> been "initialized".  Let's suppose we want to make a scheduleX object.
> Here's what we have to do:
> 
>     scheduleX := TaxRule new.	"Not usable at all".
>     scheduleX
>       breakPoint:      0 rate: 0.15;  "usable but wrong"
>       breakPoint:  22750 rate: 0.28;  "usable but wrong"
>       breakPoint:  55100 rate: 0.31;  "usable but wrong"
>       breakPoint: 115000 rate: 0.36;  "usable but wrong"
>       breakPoint: 250000 rate: 0.396. "NOW it's initialized".
> 
> What's a better approach for this class?
> Well, these tables aren't *supposed* to change.  Once you've created
> one, it had better not be tampered with.  The only reason for having
> #breakPoint:rate: is so that the thing can *really* be initialised
> (as opposed to what #initialize does).  It would be nice to design it
> so that we can provide the table declaratively:
> 
>     scheduleX := TaxRule forTable:
>       #((     0 0.15)
>         ( 22750 0.28)
>         ( 55100 0.31)
>         (115000 0.36)
>         (250000 0.396)).
>     
> So let's do that.
> 
>   Object subclass: #TaxRule
>     instanceVariableNames: 'breakPoints rates'
>     comment:
>       'I represent a tax table.
>        Given a yearly taxable income, I can give the tax rate for it.
>        I store the tax table as a sequence of amounts,
>        and the tax rate for that amount.
>        Example: in 1994 the tax rate for a single person 
> (Schedule X) was
> 	$     0 15%
> 	$ 22750 28%
> 	$ 55100 31%
> 	$115000 36%
> 	$250000 39.6%
>       '
>     category: 'private' methods:
>       initializeFromTable: anArrayOfPairs
> 	|sortedPairs|
> 	"Check that the table is well formed, has lowest break 0,
> 	 and non-decreasing rates (after sorting)."
> 
> 	(anArrayOfPairs allSatisfy: [:each |
> 	  each size == 2 and: [
> 	  each first >= 0 and: [
> 	  each second between: 0 and 1]]])
> 	    ifFalse: [self error: 'ill-formed tax table'].
> 	sortedPairs :=
> 	  anArrayOfPairs asSortedCollection: [:x :y | x first 
> <= y first].
> 	(sortedPairs first = 0)
> 	  ifFalse: [self error: 'lowest break-point is not zero'].
> 	breakPoints :=
> 	  (sortedPairs collect: [:each | each first]) 
> asSortedCollection.
> 	rates :=
> 	  (sortedPairs collect: [:each | each second]) asArray.
> 	2 to: rates size do: [:i |
> 	  ((rates at: i-1) > (rates at: i))
> 	    ifTrue: [self error: 'rates not in order']].
> 	^self
> 
>     category: 'accessing' methods:
>       rateFor: aNumber
>         "Return the tax rate for someone whose taxable income is
>          aNumber dollars."
> 	^rates at: (breakPoints 
> indexOfStartofIntervalContaining: aNumber)
> 
>     class category: 'instance creation' methods:
>       new
>         self shouldNotImplement.
>       forTable: anArrayOfPairs
>         ^super new initializeFromTable: anArrayOfPairs
> 
> Now there is no possibility of working with a TaxRule that has been
> #initialized but not initialized (a class invariant is that 
> the breakpoint
> table is non-empty and starts with 0, which #initialize did 
> not ensure),
> or partly (and thus wrongly) initialized.
> 
> There are far too many classes in Squeak which *have* a #new method
> which can create an unusable object.  It seems most unwise to 
> do anything
> to encourage this.
> 
> (By the way, this particular example just happened to be the first bit
> of someone else's code that I happened to look at after this 
> topic came
> up.  It is far too easy to find examples like this.)
> 



More information about the Squeak-dev mailing list