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 +=+