About KCP and automatic initialize

Richard A. O'Keefe ok at cs.otago.ac.nz
Thu Sep 11 23:39:54 UTC 2003


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