[comp.lang.smalltalk] method-ology

peters@sirius.UVic.CA (Doug Peters) (06/22/91)

aside{ Thanks to those who responded to my previous problem:
         Hubert Baumeister
         Chris Anderson (just where _is_ beldar, anyway :-)
         Michael Richardson (who directed me to gnu.smalltalk.bug *sorry*) &
         Steve Byrne (of course) }

Now, I have a more general Smalltalk kind of problem.
Perhaps the answer is obvious (in which case I'll learn something).
Perhaps it'll generate discussion 
  (I learned a lot from that "accessing methods" discussion a while back).

Anyway, here it is:

When should methods:
  a) modify the receiver -or-
  b) modify method parameters

For example, I have a Matrix class, and I want to have a qr method
that decomposes the receiver into (you guessed it) two matrices
traditionally labelled "q" and "r".

Should I 
  a) modify the receiver to become the "r" matrix and answer the "q"
  b) leave the receiver and answer a size two array containing two matrices
  c) have a q:r: method whose parameters become the desired results
  d) some permutation of the above
?

My thoughts so far:
  - c) sounds Fortran-ish and should be avoided. (Are there any exceptions?)
BUT
  - the strategies implied in a) or b) could become _very_ ugly for a more
complicated problem.

Any hints?
Thanks in advance
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Doug Peters (peters@sirius.UVic.CA)   "If you have any answers...
                               ...you've been asked the wrong questions."
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

voss@cs.uiuc.edu (Bill Voss) (06/22/91)

peters@sirius.UVic.CA (Doug  Peters) writes:

> (I learned a lot from that "accessing methods" discussion a while back).

Uh oh, here we go again. ;-)
[Note: My site's news feed will be down Sunday-Wed, but email will be ok.]

> For example, I have a Matrix class, and I want to have a qr method
> that decomposes the receiver into (you guessed it) two matrices
> traditionally labelled "q" and "r".
>
> Should I 
>   a) modify the receiver to become the "r" matrix and answer the "q"
>   b) leave the receiver and answer a size two array containing two matrices
>   c) have a q:r: method whose parameters become the desired results
>   d) some permutation of the above
>  ?

>   a) modify the receiver to become the "r" matrix and answer the "q"

	That tends to be dangerous.  You probably have a couple of
variables around which contain the "original" matrix, and will get
very confused when it "changes behind their back."  
(Remember, you normally only have a POINTER to an object.)

>   b) leave the receiver and answer a size two array containing two matrices

	This is a very common solution.  Quick and dirty, but at least
it is simple to implement.

>   c) have a q:r: method whose parameters become the desired results

	Getting closer.  This type of thing is usually written
as something like:
	| tmpQ tmpR |
	tmpQ := MyMatrix new.
	tmpR := MyMatrix new.
	theMatrix q: tmpQ r: tmpR.

	Programmers who anticipate that as unlikely as it seems today, 
somebody might someday subclass MyMatrix write:
	| tmpQ tmpR |
	tmpQ := theMatrix class new.
	tmpR := theMatrix class new.
	theMatrix q: tmpQ r: tmpR.

>   d) some permutation of the above

	How about some permutation from the access method discussion.
You could use DOUBLE DISPATCHING.  Add the Matrix method
	qr: anObject

		| newQ newR|
		...
		...... "You implied you already had this code"
		...
		anObject q: newQ r: newR.
		^self

and add the q:r: methods to whoever needs "q" and "r".

	If you want to be even more general (but somewhat more obscure)
you could instead add to Matrix the method:
	qr: anObject perform: aSymbol

		| newQ newR|
		...
		...... "You implied you already had this code"
		...
		anObject perform: aSymbol with: newQ with: newR.
		^self
=======================================================================
		An entirely different approach.
=======================================================================
> When should methods:
>    a) modify the receiver -or-
>    b) modify method parameters

	Sometimes.  ;-)

	More seriously, frequently it is useful to ensure that once
created, an instance never seems to change.  (Obviously this is not
always desirable.)  Numbers are generally an example of this, where
all the methods return new instances instead of changing state.

	The biggest advantage of such immutable instances is that the
programmer does not need to worry about who references the instance.

	The biggest disadvantage (in borderline cases where you could
do things either way) is usually the copy cost.  Most of the time you
make a new "immutable" instance you are copying most of the state from
an existing instance.

	If your Matrix class is implemented as an "immutable" class,
then the "q" and "r" matrices are also "immutable."  In that case You
could move "qr" into "private" and have it update two instance
variables "q" and "r" then add two access methods:
	returnQ
		q isNil ifTrue: [self qr].
		^q
and the same for a returnR method.  WARNING:  See previous "accessing
methods" discussion for warnings about using access methods!  They
still apply!

				that should generate some discussion!
				Bill Voss <voss@a.cs.uiuc.edu>

kentb@argosy.UUCP (Kent Beck) (06/24/91)

Every case I've ever seen of a method which simulated multiple valued
return by returning Arrays or (much worse) Points could be rewritten by
figuring out what kind of real object you want to return.  The need to
return multiple objects from a method tells me that I have discovered
an object, I just don't know what to call it or what its responsibilities
are yet.  In these cases I force myself (however inconvenient it seems
at the time) to create a new object which holds the multiple objects
I want to return.  I always find many unforeseen uses for these
serendipitously discovered objects. I consider this technique
a compelling example of the value of incremental design: I would never
have designed these objects up front, but even though they are discovered 
"in the heat of battle" they can be some of the most useful implementation-
level objects I create.

Kent

sbb@laplace.eng.sun.com (Steve Byrne) (06/25/91)

Another way to deal with doing "multiple value returns" is to pass in a block
which accepts as parameters the values which are to be returned.  Thus:

	foo asQr: [ :q :r | ..... ]

This means that you don't *have* to store these variables away in some
locals if you won't want to, you don't have to grab them out of some
structured object which was returned from the method, etc.  I've used
this technique in various places and it's worked out quite well.

Steve

new@ee.udel.edu (Darren New) (06/25/91)

In article <VOSS.91Jun22021049@laslo.cs.uiuc.edu> voss@cs.uiuc.edu (Bill Voss) writes:
>	Programmers who anticipate that as unlikely as it seems today, 
>somebody might someday subclass MyMatrix write:
>	| tmpQ tmpR |
>	tmpQ := theMatrix class new.
>	tmpR := theMatrix class new.
>	theMatrix q: tmpQ r: tmpR.

And those who have been screwed by somebody else's code that gets it wrong :-),
might code
     tmpQ := theMatrix species new.
     tmpR := theMatrix species new.

This would allow subclases which have specialized internal representations
(e.g., a Toplitz (sp?) matrix) to return a normal matrix when the invariant
of which the internal representation is based changes.  See, for example,
the Interval class, whose species is Array (since adding or removing 
on an interval may not continue to yield something that can be represented
as a start/stop/step tuple.
					     -- Darren

-- 
--- Darren New --- Grad Student --- CIS --- Univ. of Delaware ---
----- Network Protocols, Graphics, Programming Languages, FDTs -----
+=+ Nails work better than screws, when both are driven with hammers +=+

CWatts@BNR.CA (Carl Watts) (06/25/91)

The closest analagous situation to your Matrix>qr question is Point>r
and Point>theta.

Thats where I would take my cue for answering the Matrix question.  You
could provide
two methods Matrix>q and Matrix>r which each answer the appropriate
matrix.

But, I suspect that your algorithm for calculating the q and r matrices
goes through a significant amount of work to calculate either matrix
and must calculate the other one along the way anyway.  And I think
that you are also saying that this is the reason you don't want to have
two separate methods Matrix>q and Matrix>r since then Smalltalk would
have to go through the whole calculation twice in order to get both
Matrices.

If thats true, then instead I would probably implement a method
Matrix>withQR: that accepts a single parameter which is a block of code
to be evaluated with the q and r matrices as parameters.

That way if "m" is a matrix then you would be able to say things like:

	m withQR: [:q :r | ...... ]

this gets around all the problems in your origional message.  You don't
have to make a method that answers an Array of two Matrices.  And
(horror of horrors) you don't have to make a method thats destructive
to the receiver or the method parameters.

Of course my Matrix>q and Matrix>r solutions are more elegant if my
assumptions about your algorithm are the reverse.  I would use these
two separate methods if the work for calculating q and the work for
calculating r can be separated.

new@ee.udel.edu (Darren New) (06/25/91)

In article <1991Jun24.184824.9986@bqnes74.bnr.ca> CWatts@BNR.CA (Carl Watts) writes:
>Thats where I would take my cue for answering the Matrix question.  You
>could provide
>two methods Matrix>q and Matrix>r which each answer the appropriate
>matrix.

And if it is common that upon requesting q one often wants r (as in
r and theta), one may wish to cache each upon calling the other,
and clearing that cache whenever theeMatrix is changed.  Don't you
love OO programming :-)             -- Darren

-- 
--- Darren New --- Grad Student --- CIS --- Univ. of Delaware ---
----- Network Protocols, Graphics, Programming Languages, FDTs -----
+=+ Nails work better than screws, when both are driven with hammers +=+