General smalltalk questions..

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
19 messages Options
Reply | Threaded
Open this post in threaded view
|

General smalltalk questions..

Rick Flower
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

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Dave Stevenson-2
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
>
>

Reply | Threaded
Open this post in threaded view
|

RE: General smalltalk questions..

Boris Popov, DeepCove Labs (SNN)
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
Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Martin McClure
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

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Dave Stevenson-2
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
>>
>>
>
>

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Rick Flower
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

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Isaac Gouy-2
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 

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Nicolas Cellier-3
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'

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Vassili Bykov
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: '

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

jWarrior
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
>

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

jWarrior
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
>>
>

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Nicolas Cellier-3
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

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

jWarrior
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
>>>
>>
>

Reply | Threaded
Open this post in threaded view
|

RE: General smalltalk questions..

Steven Kelly
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

Reply | Threaded
Open this post in threaded view
|

RE: General smalltalk questions..

Terry Raymond
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

Reply | Threaded
Open this post in threaded view
|

RE: General smalltalk questions..

Terry Raymond
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>
===========================================================

Reply | Threaded
Open this post in threaded view
|

Re: General smalltalk questions..

Vassili Bykov
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: '

Reply | Threaded
Open this post in threaded view
|

RE: General smalltalk questions..

Steven Kelly
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

Reply | Threaded
Open this post in threaded view
|

RE: General smalltalk questions..

Steven Kelly
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
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