Hi all..
I've got a few questions that are not specifically tied to one flavor of ST or another.. These questions are easy to answer for either C or C++, but wasn't sure if they really map to ST or not (I suspect the 2nd does).. 1) Returning multiple values from a class method. In C/C++, I can specify a pointer or reference to multiple arguments that the method in question can modify and the caller can see those results. Is this possible in ST? Just curious.. 2) Performing (slightly) more complex conditional operations. If I want to do something like this in C++ (in english = "if a=1 or b=2 do ...": if ( (A == 1) || (B == 2) ) { ... } What's the best way to do that in Smalltalk? Surprisingly enough, I've only had one case where it would be nice to do this sort of thing.. Thanks! -- Rick |
1) You can pass in a number of objects as arguments. Those can be
modified, but not replaced (unless you resort to very low-level system tricks like #become:, but usually you don't want to do that). But if you pass in a collection object (which just happens to reference some number of other, more interesting objects), you can replace elements thereof. 2) You could do: (a = 1) | (b = 2) ifTrue: [self doSomething] but often this form is preferred: (a = 1 or: [b = 2]) ifTrue: [self doSomething] because the 'b=2' expression need not be evaluated if the first expression evaluates to true. Look at the methods in the 'controlling' protocol of class Boolean, and its subclasses. Dave Rick Flower wrote: > Hi all.. > > I've got a few questions that are not specifically tied to one flavor of > ST or another.. These questions are easy to answer for either C or C++, > but wasn't sure if they really map to ST or not (I suspect the 2nd does).. > > 1) Returning multiple values from a class method. In C/C++, I can > specify a pointer or reference to multiple arguments that the method in > question can modify and the caller can see those results. Is this > possible in ST? Just curious.. > > 2) Performing (slightly) more complex conditional operations. If I want > to do something like this in C++ (in english = "if a=1 or b=2 do ...": > > if ( (A == 1) || (B == 2) ) { ... } > > What's the best way to do that in Smalltalk? Surprisingly enough, > I've only had one case where it would be nice to do this sort of thing.. > > Thanks! > > -- Rick > > |
In reply to this post by Rick Flower
1) You could just return multiple results in a collection of some sorts,
although more often than not this pattern does not apply to Smalltalk the same way it does to C. MyClass>>doSomething: a with: b | c d | c := a + b. d := a - b. ^Array with: c with: d 2) That would be exactly what you wrote, (a = 1) | (b = 2) ifTrue: [Transcript show: 'Yes']. Or, (a = 1 or: [b = 2]) ifTrue: [Transcript show: 'Yes']. Hope this helps, -Boris -- +1.604.689.0322 DeepCove Labs Ltd. 4th floor 595 Howe Street Vancouver, Canada V6C 2T5 [hidden email] CONFIDENTIALITY NOTICE This email is intended only for the persons named in the message header. Unless otherwise indicated, it contains information that is private and confidential. If you have received it in error, please notify the sender and delete the entire message including any attachments. Thank you. -----Original Message----- From: Rick Flower [mailto:[hidden email]] Sent: Wednesday, July 19, 2006 2:45 PM To: VisualWorks Mailing List Subject: General smalltalk questions.. Hi all.. I've got a few questions that are not specifically tied to one flavor of ST or another.. These questions are easy to answer for either C or C++, but wasn't sure if they really map to ST or not (I suspect the 2nd does).. 1) Returning multiple values from a class method. In C/C++, I can specify a pointer or reference to multiple arguments that the method in question can modify and the caller can see those results. Is this possible in ST? Just curious.. 2) Performing (slightly) more complex conditional operations. If I want to do something like this in C++ (in english = "if a=1 or b=2 do ...": if ( (A == 1) || (B == 2) ) { ... } What's the best way to do that in Smalltalk? Surprisingly enough, I've only had one case where it would be nice to do this sort of thing.. Thanks! -- Rick smime.p7s (4K) Download Attachment |
In reply to this post by Rick Flower
Rick Flower wrote:
> > 1) Returning multiple values from a class method. In C/C++, I can > specify a pointer or reference to multiple arguments that the method in > question can modify and the caller can see those results. Is this > possible in ST? Just curious.. In Smalltalk, each argument is an object reference. The invoked method cannot change that reference to refer to a different object, but you can send the referred object messages, which may change that object's state. As others have pointed out, one way to use that capability is to pass a collection (such as an Array) and modify it by sending it messages (such as #at:put:). However, if you find yourself passing around Arrays of fixed length where each offset has a different fixed meaning, your code will most often get simpler and easier to read if you replace those Arrays with instances of a new class with an instance variable for each offset in the Arrays. Regards, -Martin |
In reply to this post by Dave Stevenson-2
> ... Those can be modified...
Oops, I should have said that you can ask them to modify themselves by sending them messages... Dave Dave Stevenson wrote: > 1) You can pass in a number of objects as arguments. Those can be > modified, but not replaced (unless you resort to very low-level system > tricks like #become:, but usually you don't want to do that). But if > you pass in a collection object (which just happens to reference some > number of other, more interesting objects), you can replace elements > thereof. > > 2) You could do: > (a = 1) | (b = 2) > ifTrue: [self doSomething] > but often this form is preferred: > (a = 1 or: [b = 2]) > ifTrue: [self doSomething] > because the 'b=2' expression need not be evaluated if the first > expression evaluates to true. Look at the methods in the 'controlling' > protocol of class Boolean, and its subclasses. > > Dave > > Rick Flower wrote: >> Hi all.. >> >> I've got a few questions that are not specifically tied to one flavor >> of ST or another.. These questions are easy to answer for either C or >> C++, but wasn't sure if they really map to ST or not (I suspect the >> 2nd does).. >> >> 1) Returning multiple values from a class method. In C/C++, I can >> specify a pointer or reference to multiple arguments that the method >> in question can modify and the caller can see those results. Is this >> possible in ST? Just curious.. >> >> 2) Performing (slightly) more complex conditional operations. If I >> want to do something like this in C++ (in english = "if a=1 or b=2 do >> ...": >> >> if ( (A == 1) || (B == 2) ) { ... } >> >> What's the best way to do that in Smalltalk? Surprisingly enough, >> I've only had one case where it would be nice to do this sort of thing.. >> >> Thanks! >> >> -- Rick >> >> > > |
In reply to this post by Martin McClure
Martin McClure wrote:
> Rick Flower wrote: >> >> 1) Returning multiple values from a class method. In C/C++, I can >> specify a pointer or reference to multiple arguments that the method >> in question can modify and the caller can see those results. Is this >> possible in ST? Just curious.. > > In Smalltalk, each argument is an object reference. The invoked method > cannot change that reference to refer to a different object, but you can > send the referred object messages, which may change that object's state. > > As others have pointed out, one way to use that capability is to pass a > collection (such as an Array) and modify it by sending it messages (such > as #at:put:). > > However, if you find yourself passing around Arrays of fixed length > where each offset has a different fixed meaning, your code will most > often get simpler and easier to read if you replace those Arrays with > instances of a new class with an instance variable for each offset in > the Arrays. Thanks everyone for the timely answers! They'll help me quite a bit! Thanks again! -- Rick |
In reply to this post by Rick Flower
> I've got a few questions that are not specifically
> tied to one flavor of ST or another. The comp.lang.smalltalk newsgroup is also a reasonable place to ask general Smalltalk questions http://groups.google.com/group/comp.lang.smalltalk?lnk=oa __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com |
In reply to this post by Dave Stevenson-2
You can also mimic C pointer with ValueHolder...
{void *p;} is (p := ValueHolder with: nil.) {*p} is (p value) {*p=q;} is (p value: q.) Quite heavy, but possible, see the silly example: | sum product result | sum := ValueHolder with: nil. product := ValueHolder with: nil. result := SillyAlgorithm computeSumAndProductOf: 4 and: 3 storeSumInto: sum andProductInto: product. Transcript cr; show: 'sum is ' , sum value printString. Transcript cr; show: 'product is ' , sum value printString. Transcript cr; show: 'result is ' , result printString. where: SillyAlgorithm class>>computeSumAndProductOf: a and: b storeSumInto: sum andProductInto: product sum value: a + b. product value: a * b. ^'this example is silly' |
In reply to this post by Martin McClure
Martin McClure wrote:
> However, if you find yourself passing around Arrays of fixed length > where each offset has a different fixed meaning, your code will most > often get simpler and easier to read if you replace those Arrays with > instances of a new class with an instance variable for each offset in > the Arrays. ...or a more lightweight than a class and a more descriptive than an array option is to use continuation-passing style, as something like: self parseFullFilename: aString into: [:path :name :extension | ...] -- Vassili Bykov <[hidden email]> [:s | s, s printString] value: '[s: | s, s printString] value: ' |
In reply to this post by Martin McClure
Martin McClure wrote:
> Rick Flower wrote: > >> >> 1) Returning multiple values from a class method. In C/C++, I can >> specify a pointer or reference to multiple arguments that the method >> in question can modify and the caller can see those results. Is this >> possible in ST? Just curious.. > > > In Smalltalk, each argument is an object reference. The invoked method > cannot change that reference to refer to a different object, but you > can send the referred object messages, which may change that object's > state. > > As others have pointed out, one way to use that capability is to pass > a collection (such as an Array) and modify it by sending it messages > (such as #at:put:). > > However, if you find yourself passing around Arrays of fixed length > where each offset has a different fixed meaning, your code will most > often get simpler and easier to read if you replace those Arrays with > instances of a new class with an instance variable for each offset in > the Arrays. Right. Here's a real world example. I inherited maintenance on a method that returns two values from an adjudication algorithm -- the actual result (outcomeGT) and what we think happened (outcomeBda). The method returned (Array with: outcomeGT with: outcomeBda). The calling method said something like this: outcome := callAdjudicator. outcomeGT := outcome key. outcomeBda := outcome array. This is ok, but not very clear. Then I found that I needed to also return the number of weapons and the number of counterMeasures used. This is a sure sign that a new object and a bit of refactoring is called for. Now the callAdjudicator adjudication method says something like this: ... do adjudication here ... anAswAdjudicationOutcome := AswAdjudicationOutcome new. "create new object" anAswAdjudicationOutcome outcomeGT: outcomeGT. anAswAdjudicationOutcome outcomeBda: outcomeBda. anAswAdjudicationOutcome weaponsUsed: weaponsUsed. anAswAdjudicationOutcome counterMeasuresUsed: counterMeasuresUsed. ^anAswAdjudicationOutcome This avoids returning (Array with: outcomeGT with: outcomeBda with: weaponsUsed with: counterMeasuresUsed ). Not only is your code more readable, but your calling method gets a real live object that it can do more stuff with in the future if necessary. HTH, Donald Smalltalk - The Leatherman of programming languages > > Regards, > > -Martin > |
Donald MacQueen wrote:
> Martin McClure wrote: > >> Rick Flower wrote: >> >>> >>> 1) Returning multiple values from a class method. In C/C++, I can >>> specify a pointer or reference to multiple arguments that the method >>> in question can modify and the caller can see those results. Is >>> this possible in ST? Just curious.. >> >> >> >> In Smalltalk, each argument is an object reference. The invoked >> method cannot change that reference to refer to a different object, >> but you can send the referred object messages, which may change that >> object's state. >> >> As others have pointed out, one way to use that capability is to pass >> a collection (such as an Array) and modify it by sending it messages >> (such as #at:put:). >> >> However, if you find yourself passing around Arrays of fixed length >> where each offset has a different fixed meaning, your code will most >> often get simpler and easier to read if you replace those Arrays with >> instances of a new class with an instance variable for each offset in >> the Arrays. > > > Right. Here's a real world example. I inherited maintenance on a > method that returns two values from an adjudication algorithm -- the > actual result (outcomeGT) and what we think happened (outcomeBda). The > method returned (Array with: outcomeGT with: outcomeBda). The calling > method said something like this: > > outcome := callAdjudicator. > outcomeGT := outcome key. > outcomeBda := outcome array. ^^^ WRONG! outcomeBda := outcome value. > > This is ok, but not very clear. > > Then I found that I needed to also return the number of weapons and > the number of counterMeasures used. This is a sure sign that a new > object and a bit of refactoring is called for. > > Now the callAdjudicator adjudication method says something like this: > > ... do adjudication here ... > anAswAdjudicationOutcome := AswAdjudicationOutcome new. "create new > object" > anAswAdjudicationOutcome outcomeGT: outcomeGT. > anAswAdjudicationOutcome outcomeBda: outcomeBda. > anAswAdjudicationOutcome weaponsUsed: weaponsUsed. > anAswAdjudicationOutcome counterMeasuresUsed: counterMeasuresUsed. > ^anAswAdjudicationOutcome > > This avoids returning (Array with: outcomeGT with: outcomeBda with: > weaponsUsed with: counterMeasuresUsed ). Not only is your code more > readable, but your calling method gets a real live object that it can > do more stuff with in the future if necessary. > > HTH, > > Donald > > Smalltalk - The Leatherman of programming languages > > > > >> >> Regards, >> >> -Martin >> > |
In reply to this post by jWarrior
Similarly, there are patterns like creating a class for the algorithm itself,
not only for the results. C function inputs and outputs can be mapped as inst vars, so as important auxiliaries. This enables to have - default values for some inputs - optional outputs - lazy evaluation (result computed only when an output is queried) I used this pattern in Smallapack (on public store), for example for computing eigenValues of a Matrix. I can have several outputs (eigenVectors), several choice about algorithm,... And happily, my long un-smalltalkish algortihm have been split into smaller reusable methods (by inheritance of algorithms for symmetric matrices, complex matrices, etc...). This can be transposed to complex operations involving both computation of lot of outputs, and lot of variants and default values on inputs. Nicolas Le Jeudi 20 Juillet 2006 03:16, Donald MacQueen a écrit : > > Right. Here's a real world example. I inherited maintenance on a method > that returns two values from an adjudication algorithm -- the actual > result (outcomeGT) and what we think happened (outcomeBda). The method > returned (Array with: outcomeGT with: outcomeBda). The calling method > said something like this: > > outcome := callAdjudicator. > outcomeGT := outcome key. > outcomeBda := outcome array. > > This is ok, but not very clear. > > Then I found that I needed to also return the number of weapons and the > number of counterMeasures used. This is a sure sign that a new object > and a bit of refactoring is called for. > > Now the callAdjudicator adjudication method says something like this: > > ... do adjudication here ... > anAswAdjudicationOutcome := AswAdjudicationOutcome new. "create new object" > anAswAdjudicationOutcome outcomeGT: outcomeGT. > anAswAdjudicationOutcome outcomeBda: outcomeBda. > anAswAdjudicationOutcome weaponsUsed: weaponsUsed. > anAswAdjudicationOutcome counterMeasuresUsed: counterMeasuresUsed. > ^anAswAdjudicationOutcome > > This avoids returning (Array with: outcomeGT with: outcomeBda with: > weaponsUsed with: counterMeasuresUsed ). Not only is your code more > readable, but your calling method gets a real live object that it can do > more stuff with in the future if necessary. > > HTH, > > Donald > > Smalltalk - The Leatherman of programming languagesqueried > > > Regards, > > > > -Martin |
In reply to this post by jWarrior
Donald MacQueen wrote:
> Donald MacQueen wrote: > >> [snip] >> >> Right. Here's a real world example. I inherited maintenance on a >> method that returns two values from an adjudication algorithm -- the >> actual result (outcomeGT) and what we think happened (outcomeBda). >> The method returned (Array with: outcomeGT with: outcomeBda). The >> calling method said something like this: >> >> outcome := callAdjudicator. >> outcomeGT := outcome key. >> outcomeBda := outcome array. > > > ^^^ WRONG! outcomeBda := outcome value. Wrong again!. I am mixing my metaphors here. The callAdjudicator method returned an Association (Association key: outcomeGT value: outcomeBda) which the calling method decoded as above. It could also have returned Array with: outcomeGT with: outcomeBda, and then the calling method would look like this: outcome := callAdjudicator. outcomeGT := outcome at: 1. outcomeBda := outcome at: 2. which is even less clear. Either way of returning these two values requires the person seeing this method for the first time to look at the callAdjudicator unnecessarily. The longer I program, the more convinced I am that clarity in code is the highest virtue. Now if I could just apply that to my postings ... > >> >> This is ok, but not very clear. >> >> Then I found that I needed to also return the number of weapons and >> the number of counterMeasures used. This is a sure sign that a new >> object and a bit of refactoring is called for. >> >> Now the callAdjudicator adjudication method says something like this: >> >> ... do adjudication here ... >> anAswAdjudicationOutcome := AswAdjudicationOutcome new. "create new >> object" >> anAswAdjudicationOutcome outcomeGT: outcomeGT. >> anAswAdjudicationOutcome outcomeBda: outcomeBda. >> anAswAdjudicationOutcome weaponsUsed: weaponsUsed. >> anAswAdjudicationOutcome counterMeasuresUsed: counterMeasuresUsed. >> ^anAswAdjudicationOutcome >> >> This avoids returning (Array with: outcomeGT with: outcomeBda with: >> weaponsUsed with: counterMeasuresUsed ). Not only is your code more >> readable, but your calling method gets a real live object that it can >> do more stuff with in the future if necessary. >> >> HTH, >> >> Donald >> >> Smalltalk - The Leatherman of programming languages >> >> >> >> >>> >>> Regards, >>> >>> -Martin >>> >> > |
In reply to this post by Rick Flower
From: nicolas cellier [mailto:[hidden email]]
> You can also mimic C pointer with ValueHolder... > > {void *p;} is (p := ValueHolder with: nil.) > {*p} is (p value) > {*p=q;} is (p value: q.) I recently had occasion to use that, and maybe a real example would help to understand it. I was trying to refactor some code for writing a file in one of three different ways. The structure was roughly: [ [operation == #merge ifTrue: [...]. operation == #write ifTrue: [...]. operation == #append ifTrue: [...]. ] on: SomeError do: [...] ] ensure: [stream ifNotNil: [stream close]] Each of the three alternative blocks did some work and at some point often opened a stream and assigned it to the "stream" temp. Each alternative had its own idea about whether it needed to write (e.g. an append of zero characters was optimized out, as was a write where the contents on the disk were already the same). The error handling varied depending on whether there was a stream or not. Normally, we would refactor the three alternative operation blocks to their own methods - merge, write and append - and call them with "self perform: operation". That didn't work here because of the need for the main method to know the value of stream - not just at the end, but also if an error was raised. Changing stream to a ValueHolder was a good solution: the new operation methods received it as an argument, and set its value when needed. HTH, Steve |
Not to be picky, but Steve's example has an alternative
implementation that usually in the long run results in more maintainable code. That would be to make the operation an object instead of a symbol. [ [operation operateOn: stream] on: SomeError do: [...] ] ensure: [stream ifNotNil: [stream close]] I found out that way too many times I would start out using a symbol, like Steve has, and watch my code slowly get more complex. Sometimes I would recognize it and refactor changing it to use a real object. One of my problems is that much of my experience is with computers that were always under powered and it seemed to me that creating small specialized classes would consume more than necessary. Well, now I have experience that tells me otherwise. Terry =========================================================== Terry Raymond Smalltalk Professional Debug Package Crafted Smalltalk 80 Lazywood Ln. Tiverton, RI 02878 (401) 624-4517 [hidden email] <http://www.craftedsmalltalk.com> =========================================================== > -----Original Message----- > From: Steven Kelly [mailto:[hidden email]] > Sent: Thursday, July 20, 2006 5:14 AM > To: [hidden email] > Subject: RE: General smalltalk questions.. > > From: nicolas cellier [mailto:[hidden email]] > > You can also mimic C pointer with ValueHolder... > > > > {void *p;} is (p := ValueHolder with: nil.) > > {*p} is (p value) > > {*p=q;} is (p value: q.) > > I recently had occasion to use that, and maybe a real example would help > to understand it. I was trying to refactor some code for writing a file > in one of three different ways. The structure was roughly: > > [ [operation == #merge ifTrue: > [...]. > operation == #write ifTrue: > [...]. > operation == #append ifTrue: > [...]. > ] on: SomeError do: [...] > ] ensure: > [stream ifNotNil: [stream close]] > > Each of the three alternative blocks did some work and at some point > often opened a stream and assigned it to the "stream" temp. Each > alternative had its own idea about whether it needed to write (e.g. an > append of zero characters was optimized out, as was a write where the > contents on the disk were already the same). The error handling varied > depending on whether there was a stream or not. > > Normally, we would refactor the three alternative operation blocks to > their own methods - merge, write and append - and call them with "self > perform: operation". That didn't work here because of the need for the > main method to know the value of stream - not just at the end, but also > if an error was raised. Changing stream to a ValueHolder was a good > solution: the new operation methods received it as an argument, and set > its value when needed. > > HTH, > Steve |
In reply to this post by jWarrior
> -----Original Message----- > From: Donald MacQueen [mailto:[hidden email]] > Sent: Wednesday, July 19, 2006 10:20 PM > To: VisualWorks Mailing List > Cc: Rick Flower > Subject: Re: General smalltalk questions.. [stuff deleted] > The longer I program, the more convinced I am that clarity in code is > the highest virtue. A very BIG amen to that!!! Terry =========================================================== Terry Raymond Smalltalk Professional Debug Package Crafted Smalltalk 80 Lazywood Ln. Tiverton, RI 02878 (401) 624-4517 [hidden email] <http://www.craftedsmalltalk.com> =========================================================== |
In reply to this post by Terry Raymond
> -----Original Message-----
> From: Steven Kelly [mailto:[hidden email]] > Normally, we would refactor the three alternative operation blocks to > their own methods - merge, write and append - and call them with "self > perform: operation". That didn't work here because of the need for the > main method to know the value of stream - not just at the end, but also > if an error was raised. Changing stream to a ValueHolder was a good > solution: the new operation methods received it as an argument, and set > its value when needed. Hi Steve, I wonder if you would consider this an improvement, in the same vein as my yesterday's version of multiple return values. | stream operation | ... [operation ifMerge: [...] ifWrite: [...] ifAppend: [...]] on: SomeError do: [...] ] ensure: [stream ifNotNil: [stream close]] This assumes operations are turned from symbol to real objects, but of course there are other ways to do the same kind of dispatching. -- Vassili Bykov <[hidden email]> [:s | s, s printString] value: '[s: | s, s printString] value: ' |
In reply to this post by Rick Flower
> From: Vassili Bykov [mailto:[hidden email]]
> > From: Steven Kelly [mailto:[hidden email]] > > Normally, we would refactor the three alternative operation blocks to > > their own methods - merge, write and append - and call them with "self > > perform: operation". That didn't work here because of the need for the > > main method to know the value of stream - not just at the end, but also > > if an error was raised. Changing stream to a ValueHolder was a good > > solution: the new operation methods received it as an argument, and set > > its value when needed. > > I wonder if you would consider this an improvement, in the same vein as > my yesterday's version of multiple return values. > > | stream operation | > ... > [operation > ifMerge: [...] > ifWrite: [...] > ifAppend: [...]] > on: SomeError do: [...] > ] ensure: > [stream ifNotNil: [stream close]] > > This assumes operations are turned from symbols to real objects I presume your intention is that the new Operation class would have three concrete subclasses, each implementing ifMerge:ifWrite:ifAppend: to only actually run one of the blocks, like Boolean ifTrue:ifFalse:. In my case, that wouldn't have helped at all, but you weren't to know! The reason I wanted to refactor was that the amount of code in each ... section was large. Another reason it wouldn't work here is that the set of operations was growing. Whilst I could have used a refactoring to append ifNewOp: to the above selector, it would still have felt like a fairly large change. The imposition of an arbitrary order on the operations also doesn't thrill me - Booleans have to implement ifFalse:ifTrue: too, but I'd hate to do that for 4 operations - giving 4!=24 selectors :-). I think the above 'Boolean-style' approach is best suited to structures where the operation name is not semantically linked to the content of the block. E.g. with Boolean the ifTrue: block's contents have nothing particularly "truth-related" about them - add #not to the condition and they could equally well be in the ifFalse: block. In my case, the contents of the #merge, #write and #append blocks were precisely the implementation of merging, writing and appending. Semantically I'd thus be happier moving them to three methods with "self perform: operation", or to three new classes (e.g. concrete subclasses of the current class). As this area of the app is already class-heavy (many small classes), and this is the only bit of code that varies according to the kind of operation, I went for the three methods. Thanks for the suggestion though - I'll keep my eyes peeled for a chance to apply the "parseFilename: fn into: [:dir :file :ext ...]" or Boolean-style continuation passing patterns you pointed out! Steve |
In reply to this post by Rick Flower
> From: Terry Raymond [mailto:[hidden email]]
> > Not to be picky, but Steve's example has an alternative > implementation that usually in the long run results in > more maintainable code. > > That would be to make the operation an object instead > of a symbol. > > [ [operation operateOn: stream] > on: SomeError > do: [...] > ] ensure: > [stream ifNotNil: [stream close]] Yes, you're right Terry. As I mentioned in my reply to Vassili there were reasons in this case to have merge, write and append be implemented as correspondingly-named methods rather than classes. For me, one perform: and three methods is "lighter" than three classes each with just one operateOn: method. As a broad generalization, we work best if a large mass of information is presented as a tree with 7 +/- 2 branches from every node. In VW the levels in the tree are bundle, package, class, protocol, method (plus the orthogonal class hierarchy tree). The package in question had around 100 classes, and the class in question had 3 protocols with about 5 methods in each. I added a new protocol for the operation methods, which left a nice-looking "tree". If I'd added subclasses, that would have been 3 more classes, each with 1 protocol containing 1 method. Just to be picky :), your implementation above misses the point of my original post: the individual operations needed to assign to stream. That was one reason they hadn't been separated out earlier. My solution - using streamHolder - would of course work fine in your way too. And of course I have the totally unfair advantage in these discussions of seeing the whole application structure and all the code. Some of it was ugly too, which is why I replaced it with ...! Steve > > -----Original Message----- > > From: Steven Kelly [mailto:[hidden email]] > > Sent: Thursday, July 20, 2006 5:14 AM > > To: [hidden email] > > Subject: RE: General smalltalk questions.. > > > > From: nicolas cellier [mailto:[hidden email]] > > > You can also mimic C pointer with ValueHolder... > > > > > > {void *p;} is (p := ValueHolder with: nil.) > > > {*p} is (p value) > > > {*p=q;} is (p value: q.) > > > > I recently had occasion to use that, and maybe a real example would > > to understand it. I was trying to refactor some code for writing a file > > in one of three different ways. The structure was roughly: > > > > [ [operation == #merge ifTrue: > > [...]. > > operation == #write ifTrue: > > [...]. > > operation == #append ifTrue: > > [...]. > > ] on: SomeError do: [...] > > ] ensure: > > [stream ifNotNil: [stream close]] > > > > Each of the three alternative blocks did some work and at some point > > often opened a stream and assigned it to the "stream" temp. Each > > alternative had its own idea about whether it needed to write (e.g. > > append of zero characters was optimized out, as was a write where the > > contents on the disk were already the same). The error handling varied > > depending on whether there was a stream or not. > > > > Normally, we would refactor the three alternative operation blocks to > > their own methods - merge, write and append - and call them with "self > > perform: operation". That didn't work here because of the need for the > > main method to know the value of stream - not just at the end, but also > > if an error was raised. Changing stream to a ValueHolder was a good > > solution: the new operation methods received it as an argument, and set > > its value when needed. > > > > HTH, > > Steve |
Free forum by Nabble | Edit this page |