how to write this without a if then

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

how to write this without a if then

Roelof
Hello,

Yesterday I had a talk with luc frabresse about using if then.
He said if I understand it right, Its the best to not using a if then or
a ifTrue/ifFalse.

Can anyone help me figure out how to rewrite this project so I will not
use the ifTrue in the basement function.

my code so far can be found here : https://github.com/RoelofWobben/AOC2015

Roelof


Reply | Threaded
Open this post in threaded view
|

Re: how to write this without a if then

Dale Henrichs-3

Roelof,

One technique to eliminating the use of #ifTrue:ifFalse: is to use double dispatching. There are some good examples of using double dispatching in Ralph Johnson's paper "ARITHMETIC AND DOUBLE DISPATCHING IN SMALLTALK-80"[1].  you should be able to get the basic idea by skimming the smalltalk code examples.

I used double dispatching fairly extensively in Metacello ...

In practice you may not eliminate all use of #ifTrue:ifFalse:, but double dispatching works quite well in places where you are tempted to do type checking...

Dale

[1] https://www.researchgate.net/publication/239578755_Arithmetic_and_double-_dispatching_in_smalltalk-80

On 11/27/18 8:40 AM, Roelof Wobben wrote:
Hello,

Yesterday I had a talk with luc frabresse about using if then.
He said if I understand it right, Its the best to not using a if then or a ifTrue/ifFalse.

Can anyone help me figure out how to rewrite this project so I will not use the ifTrue in the basement function.

my code so far can be found here : https://github.com/RoelofWobben/AOC2015

Roelof


Reply | Threaded
Open this post in threaded view
|

Re: how to write this without a if then

Richard O'Keefe
In reply to this post by Roelof
The idea of a flat ban on #ifTrue:ifFalse: is ridiculous.
Number>>abs
   ^self negative ifTrue: [self negated] ifFalse: [self]

Is there any better way to do this?  Not really, you can
only move the #ifTrue:ifFalse: somewhere else.

The main argument against #ifTrue:ifFalse: is NOT TO USE
IT FOR TYPE TESTS.  If you want to do different things
depending on the class of x, ask x to do it.  (The irony
is that in Smalltalk, #ifTrue:ifFalse: *is* in principle
an object-oriented dispatch.)  If you want to do
different things depending on the state of x, and this
requires revealing internal details that would not
otherwise be revealed, ask x to do it.

Two key ideas in software engineering are coupling
(lots of interdependencies makes things hard to re-use)
and cohesion (modules/types/classes should be "about"
one thing).  You have to balance the "use OO dispatch"
idea against this: you have a method in class A that
depends on an object of class B, and you don't want
A to have to know too much about B, but on the other
hand, you don't want B to have to depend too much on
what A is up to.  If it makes sense to have a B without
any A around, then changes to A should not really
require changes to B.

So eliminating #ifTrue:ifFalse: from your code can make
it WORSE.  You have to THINK about each task and decide
where it REALLY belongs.  Is this bit of code entirely
about B?  Then it belongs in B.  Is that bit of code
about already public information concerning B and also
tied to A's needs and wants?  Then it belongs in A.  And
if that means using some sort of "if", go ahead!

The Pharo 6 sources contain about 5800 classes and
over 40000 ifTrue:/ifFalse:/ifNil:/ifNotNil: uses.
The Dolphin core contains about 2100 clases and
over 12600 ifs.
My Smalltalk has about 13 ifs per class.
Smalltalk/X (JV) has about 6500 classes and
over 127000 ifs, nearly 20 per class.

 

On Wed, 28 Nov 2018 at 05:41, Roelof Wobben <[hidden email]> wrote:
Hello,

Yesterday I had a talk with luc frabresse about using if then.
He said if I understand it right, Its the best to not using a if then or
a ifTrue/ifFalse.

Can anyone help me figure out how to rewrite this project so I will not
use the ifTrue in the basement function.

my code so far can be found here : https://github.com/RoelofWobben/AOC2015

Roelof


Reply | Threaded
Open this post in threaded view
|

Re: how to write this without a if then

Sven Van Caekenberghe-2


> On 28 Nov 2018, at 06:15, Richard O'Keefe <[hidden email]> wrote:
>
> The idea of a flat ban on #ifTrue:ifFalse: is ridiculous.
> Number>>abs
>    ^self negative ifTrue: [self negated] ifFalse: [self]
>
> Is there any better way to do this?  Not really, you can
> only move the #ifTrue:ifFalse: somewhere else.
>
> The main argument against #ifTrue:ifFalse: is NOT TO USE
> IT FOR TYPE TESTS.  If you want to do different things
> depending on the class of x, ask x to do it.  (The irony
> is that in Smalltalk, #ifTrue:ifFalse: *is* in principle
> an object-oriented dispatch.)  If you want to do
> different things depending on the state of x, and this
> requires revealing internal details that would not
> otherwise be revealed, ask x to do it.
>
> Two key ideas in software engineering are coupling
> (lots of interdependencies makes things hard to re-use)
> and cohesion (modules/types/classes should be "about"
> one thing).  You have to balance the "use OO dispatch"
> idea against this: you have a method in class A that
> depends on an object of class B, and you don't want
> A to have to know too much about B, but on the other
> hand, you don't want B to have to depend too much on
> what A is up to.  If it makes sense to have a B without
> any A around, then changes to A should not really
> require changes to B.
>
> So eliminating #ifTrue:ifFalse: from your code can make
> it WORSE.  You have to THINK about each task and decide
> where it REALLY belongs.  Is this bit of code entirely
> about B?  Then it belongs in B.  Is that bit of code
> about already public information concerning B and also
> tied to A's needs and wants?  Then it belongs in A.  And
> if that means using some sort of "if", go ahead!

Very well written, thanks.

> The Pharo 6 sources contain about 5800 classes and
> over 40000 ifTrue:/ifFalse:/ifNil:/ifNotNil: uses.
> The Dolphin core contains about 2100 clases and
> over 12600 ifs.
> My Smalltalk has about 13 ifs per class.
> Smalltalk/X (JV) has about 6500 classes and
> over 127000 ifs, nearly 20 per class.
>
>  
>
> On Wed, 28 Nov 2018 at 05:41, Roelof Wobben <[hidden email]> wrote:
> Hello,
>
> Yesterday I had a talk with luc frabresse about using if then.
> He said if I understand it right, Its the best to not using a if then or
> a ifTrue/ifFalse.
>
> Can anyone help me figure out how to rewrite this project so I will not
> use the ifTrue in the basement function.
>
> my code so far can be found here : https://github.com/RoelofWobben/AOC2015
>
> Roelof
>
>


Reply | Threaded
Open this post in threaded view
|

Re: how to write this without a if then

Tim Mackinnon
In reply to this post by Richard O'Keefe

> The idea of a flat ban on #ifTrue:ifFalse: is ridiculous.
> Number>>abs
>    ^self negative ifTrue: [self negated] ifFalse: [self]
>
> Is there any better way to do this?  Not really, you can
> only move the #ifTrue:ifFalse: somewhere else.

But still it’s always a good thought experiment to think about how to do it without the if, as many more interesting objects and approaches can come out of it.

From above - it might be interesting if positive numbers were different from negative ones ... I’m not saying you would do it (there are often trade offs) but it’s too easy to stick in an if and miss out on a richer domain.

Tim

Reply | Threaded
Open this post in threaded view
|

Re: how to write this without a if then

Richard O'Keefe
In reply to this post by Roelof
In Advent-of-Code 2015, the first problem is
really quite simple.  There are at least two
ways to think about it.
"CS101":
   set a counter to 0
   for each character of the string
     if it is '(' increment the counter
     if it is ')' decrement the counter
   report the counter

"Higher level":
   report the difference between
     (the number of '(' characters in the string) and
     (the number of ')' characters in the string).

Expressed in Smalltalk this looks something like
  Transcript print:
    (s occurrencesOf: $() - (s occurrencesOf: $)); cr.

Make no mistake: you *cannot* tell the difference
between $( and $) using class-based dispatch because
they belong to the same class.  There has to be an
"if" somewhere, the question is not whether but where.
In this case, counting the number of occurrences of an
object in a collection is has been a standard Collection
method for nearly 40 years; it's one of the basic
operations you need to learn.

The "higher level" approach can be less efficient that
the "CS101" approach, but in a case like this we really
do not care.  We want the code to be *clear*.

What about the second part of the problem?
Not having submitted any answers, I can't actually
see the second part on the AOC site, but luckily you
have included it in your program.

We want to
   [find the first place] where
      [the cumulative sum] of (c=$()
      [minus]
      [the cumulative sum] of (c=$))
      equals -1.

The "CS101" approach is
   n := i := 0.
   while n >= 0 and i < size(s) do
      i +:= 1
      if s[i] = $( then n := n + 1
      if s[i] = $) then n := n - 1
   report n

The "higher level" approach looks something like
  n := (s cumCount: [:each | each = $(]) -
       (s cumCount: [:each | each = $)])
       indexOf: -1.
-- although it gives 0 instead of s size + 1 when
-1 is never reached.

Here #indexOf: is standard, #- is defined on sequences
in Squeak and Pharo, but #cumCount: does not
exist.  So we need something like

    cumCount: aBlock       
      |c a|       
      a := Array new: self size.
      c := 0.
      self withIndexDo: [:each :i |
        (aBlock value: each) ifTrue: [c := c + 1].
        a at: i put: c].
      ^a
This is *not* coupled to the particular use we have
in mind for it; it is in no way tied to characters
or strings.  It's quite general.

Note: we do not need any new classes, except maybe
a place to put one problem-specific method.

While this answer, with no loop and no if in the
problem-specific code, is quite pretty, it has a
problem.  Suppose the string to have M characters
and the desired step to be number K.  The CS101
approach takes O(K) time and no allocations, but
the higher level approach takes O(M) time and
allocates three M-element Arrays.  (In a non-strict
functional language like Haskell, the higher level
version *also* takes O(K) time, and with a good
enough "deforesting" compiler should allocate no
data structures.)

For a problem like this, I really don't care about
the efficiency aspect.  If I *do* care about that,
then starting from a higher level version gives me
something to test a lower level version against.

To get an efficient answer to the second part,
we still don't need a new semantic class, just
some place to put the code.

Day1
  class methods:
    indexOfFirstBasementTime: steps
      |floor|
      floor := 0.
      steps keysAndValuesDo: [:i :each |
        each = $( ifTrue: [floor := floor + 1].
        each = $) ifTrue: [floor := floor - 1].
        floor = -1 ifTrue: [^i]].
      ^0 "same convention as #indexOf:"

Does this contain "if"?  Why yes, it does.
Is there any problem with that?  Why no, there isn't.
You need to treat members of the same class (left
parenthesis, right parenthesis, others) differently.
You need to treat members of the same class (minus
one, all other integers) differently.
Would there be any gain in clarity or maintainability
if these ifs were somehow eliminated?  Certainly NOT.

Quite the reverse, in fact.
input2 withIndexDo: [ :element :index | |action| 
action:= SantaAction getActionFor: element. floor := action  doMovementFor: floor .
self hasReachedBasement 
ifTrue: [^ index]].
    ^ '-2'.

There is only one word for this: obfuscated.
I was initially puzzled by your returning -2
instead of the conventional 0 if the basement
is not reached, and then *deeply confused* by
the fact that you are returning a *string* in
this case.

Looking at your code, I was further confused
by variables called 'aSymbol' whose value is
always and only a Character, never a Symbol.
And if I am wrong that
IllegalMoveSanta class>> canHandleInput:
in Smalltalk is to return 0 when something is
      
approach



We want to find the first place where something
becomes true.  There are again at least to approaches. 

On Wed, 28 Nov 2018 at 05:41, Roelof Wobben <[hidden email]> wrote:
Hello,

Yesterday I had a talk with luc frabresse about using if then.
He said if I understand it right, Its the best to not using a if then or
a ifTrue/ifFalse.

Can anyone help me figure out how to rewrite this project so I will not
use the ifTrue in the basement function.

my code so far can be found here : https://github.com/RoelofWobben/AOC2015

Roelof


Reply | Threaded
Open this post in threaded view
|

Re: how to write this without a if then

Richard O'Keefe
DRAT!  What (genius negated) designed gmail's interface?

If I am wrong that the canHandleInput: class method of
IllegalMoveSanta should return false, not true (because
the order of the elements of #subclasses is not defined,
so that IllegalMoveSanta might *always* be selected),
then that is further evidence that avoiding "if" made
the code LESS readable.

You are a beginner at this, doing all the right things
for a beginner to do.  One of the things you have to do
is to develop a sense of "smell" for code.  Each class
ought to "pull its weight".  Four classes -- which it
really does not make sense to instantiate, just put the
instance methods on the class side and drop the #new --
just to avoid a couple of very clear "ifs"?  I don't
think so.  IF THESE CLASSES NEEDED TO EXIST FOR SOME
OTHER REASON, if they had real work to do, sure.  But
they don't.  They only bloat and obscure the code.



On Wed, 28 Nov 2018 at 20:54, Richard O'Keefe <[hidden email]> wrote:
In Advent-of-Code 2015, the first problem is
really quite simple.  There are at least two
ways to think about it.
"CS101":
   set a counter to 0
   for each character of the string
     if it is '(' increment the counter
     if it is ')' decrement the counter
   report the counter

"Higher level":
   report the difference between
     (the number of '(' characters in the string) and
     (the number of ')' characters in the string).

Expressed in Smalltalk this looks something like
  Transcript print:
    (s occurrencesOf: $() - (s occurrencesOf: $)); cr.

Make no mistake: you *cannot* tell the difference
between $( and $) using class-based dispatch because
they belong to the same class.  There has to be an
"if" somewhere, the question is not whether but where.
In this case, counting the number of occurrences of an
object in a collection is has been a standard Collection
method for nearly 40 years; it's one of the basic
operations you need to learn.

The "higher level" approach can be less efficient that
the "CS101" approach, but in a case like this we really
do not care.  We want the code to be *clear*.

What about the second part of the problem?
Not having submitted any answers, I can't actually
see the second part on the AOC site, but luckily you
have included it in your program.

We want to
   [find the first place] where
      [the cumulative sum] of (c=$()
      [minus]
      [the cumulative sum] of (c=$))
      equals -1.

The "CS101" approach is
   n := i := 0.
   while n >= 0 and i < size(s) do
      i +:= 1
      if s[i] = $( then n := n + 1
      if s[i] = $) then n := n - 1
   report n

The "higher level" approach looks something like
  n := (s cumCount: [:each | each = $(]) -
       (s cumCount: [:each | each = $)])
       indexOf: -1.
-- although it gives 0 instead of s size + 1 when
-1 is never reached.

Here #indexOf: is standard, #- is defined on sequences
in Squeak and Pharo, but #cumCount: does not
exist.  So we need something like

    cumCount: aBlock       
      |c a|       
      a := Array new: self size.
      c := 0.
      self withIndexDo: [:each :i |
        (aBlock value: each) ifTrue: [c := c + 1].
        a at: i put: c].
      ^a
This is *not* coupled to the particular use we have
in mind for it; it is in no way tied to characters
or strings.  It's quite general.

Note: we do not need any new classes, except maybe
a place to put one problem-specific method.

While this answer, with no loop and no if in the
problem-specific code, is quite pretty, it has a
problem.  Suppose the string to have M characters
and the desired step to be number K.  The CS101
approach takes O(K) time and no allocations, but
the higher level approach takes O(M) time and
allocates three M-element Arrays.  (In a non-strict
functional language like Haskell, the higher level
version *also* takes O(K) time, and with a good
enough "deforesting" compiler should allocate no
data structures.)

For a problem like this, I really don't care about
the efficiency aspect.  If I *do* care about that,
then starting from a higher level version gives me
something to test a lower level version against.

To get an efficient answer to the second part,
we still don't need a new semantic class, just
some place to put the code.

Day1
  class methods:
    indexOfFirstBasementTime: steps
      |floor|
      floor := 0.
      steps keysAndValuesDo: [:i :each |
        each = $( ifTrue: [floor := floor + 1].
        each = $) ifTrue: [floor := floor - 1].
        floor = -1 ifTrue: [^i]].
      ^0 "same convention as #indexOf:"

Does this contain "if"?  Why yes, it does.
Is there any problem with that?  Why no, there isn't.
You need to treat members of the same class (left
parenthesis, right parenthesis, others) differently.
You need to treat members of the same class (minus
one, all other integers) differently.
Would there be any gain in clarity or maintainability
if these ifs were somehow eliminated?  Certainly NOT.

Quite the reverse, in fact.
input2 withIndexDo: [ :element :index | |action| 
action:= SantaAction getActionFor: element. floor := action  doMovementFor: floor .
self hasReachedBasement 
ifTrue: [^ index]].
    ^ '-2'.

There is only one word for this: obfuscated.
I was initially puzzled by your returning -2
instead of the conventional 0 if the basement
is not reached, and then *deeply confused* by
the fact that you are returning a *string* in
this case.

Looking at your code, I was further confused
by variables called 'aSymbol' whose value is
always and only a Character, never a Symbol.
And if I am wrong that
IllegalMoveSanta class>> canHandleInput:
in Smalltalk is to return 0 when something is
      
approach



We want to find the first place where something
becomes true.  There are again at least to approaches. 

On Wed, 28 Nov 2018 at 05:41, Roelof Wobben <[hidden email]> wrote:
Hello,

Yesterday I had a talk with luc frabresse about using if then.
He said if I understand it right, Its the best to not using a if then or
a ifTrue/ifFalse.

Can anyone help me figure out how to rewrite this project so I will not
use the ifTrue in the basement function.

my code so far can be found here : https://github.com/RoelofWobben/AOC2015

Roelof


Reply | Threaded
Open this post in threaded view
|

Re: how to write this without a if then

Roelof
Thanks for all the explanations.

and yes, I have to get a feel when I need a new class and when not.
and when it's "allowed" to use a if then and when not.

and if I understand the explanations I did not ask a class something so a if then is
allowed.

Roelof



Op 28-11-2018 om 09:01 schreef Richard O'Keefe:
DRAT!  What (genius negated) designed gmail's interface?

If I am wrong that the canHandleInput: class method of
IllegalMoveSanta should return false, not true (because
the order of the elements of #subclasses is not defined,
so that IllegalMoveSanta might *always* be selected),
then that is further evidence that avoiding "if" made
the code LESS readable.

You are a beginner at this, doing all the right things
for a beginner to do.  One of the things you have to do
is to develop a sense of "smell" for code.  Each class
ought to "pull its weight".  Four classes -- which it
really does not make sense to instantiate, just put the
instance methods on the class side and drop the #new --
just to avoid a couple of very clear "ifs"?  I don't
think so.  IF THESE CLASSES NEEDED TO EXIST FOR SOME
OTHER REASON, if they had real work to do, sure.  But
they don't.  They only bloat and obscure the code.



On Wed, 28 Nov 2018 at 20:54, Richard O'Keefe <[hidden email]> wrote:
In Advent-of-Code 2015, the first problem is
really quite simple.  There are at least two
ways to think about it.
"CS101":
   set a counter to 0
   for each character of the string
     if it is '(' increment the counter
     if it is ')' decrement the counter
   report the counter

"Higher level":
   report the difference between
     (the number of '(' characters in the string) and
     (the number of ')' characters in the string).

Expressed in Smalltalk this looks something like
  Transcript print:
    (s occurrencesOf: $() - (s occurrencesOf: $)); cr.

Make no mistake: you *cannot* tell the difference
between $( and $) using class-based dispatch because
they belong to the same class.  There has to be an
"if" somewhere, the question is not whether but where.
In this case, counting the number of occurrences of an
object in a collection is has been a standard Collection
method for nearly 40 years; it's one of the basic
operations you need to learn.

The "higher level" approach can be less efficient that
the "CS101" approach, but in a case like this we really
do not care.  We want the code to be *clear*.

What about the second part of the problem?
Not having submitted any answers, I can't actually
see the second part on the AOC site, but luckily you
have included it in your program.

We want to
   [find the first place] where
      [the cumulative sum] of (c=$()
      [minus]
      [the cumulative sum] of (c=$))
      equals -1.

The "CS101" approach is
   n := i := 0.
   while n >= 0 and i < size(s) do
      i +:= 1
      if s[i] = $( then n := n + 1
      if s[i] = $) then n := n - 1
   report n

The "higher level" approach looks something like
  n := (s cumCount: [:each | each = $(]) -
       (s cumCount: [:each | each = $)])
       indexOf: -1.
-- although it gives 0 instead of s size + 1 when
-1 is never reached.

Here #indexOf: is standard, #- is defined on sequences
in Squeak and Pharo, but #cumCount: does not
exist.  So we need something like

    cumCount: aBlock       
      |c a|       
      a := Array new: self size.
      c := 0.
      self withIndexDo: [:each :i |
        (aBlock value: each) ifTrue: [c := c + 1].
        a at: i put: c].
      ^a
This is *not* coupled to the particular use we have
in mind for it; it is in no way tied to characters
or strings.  It's quite general.

Note: we do not need any new classes, except maybe
a place to put one problem-specific method.

While this answer, with no loop and no if in the
problem-specific code, is quite pretty, it has a
problem.  Suppose the string to have M characters
and the desired step to be number K.  The CS101
approach takes O(K) time and no allocations, but
the higher level approach takes O(M) time and
allocates three M-element Arrays.  (In a non-strict
functional language like Haskell, the higher level
version *also* takes O(K) time, and with a good
enough "deforesting" compiler should allocate no
data structures.)

For a problem like this, I really don't care about
the efficiency aspect.  If I *do* care about that,
then starting from a higher level version gives me
something to test a lower level version against.

To get an efficient answer to the second part,
we still don't need a new semantic class, just
some place to put the code.

Day1
  class methods:
    indexOfFirstBasementTime: steps
      |floor|
      floor := 0.
      steps keysAndValuesDo: [:i :each |
        each = $( ifTrue: [floor := floor + 1].
        each = $) ifTrue: [floor := floor - 1].
        floor = -1 ifTrue: [^i]].
      ^0 "same convention as #indexOf:"

Does this contain "if"?  Why yes, it does.
Is there any problem with that?  Why no, there isn't.
You need to treat members of the same class (left
parenthesis, right parenthesis, others) differently.
You need to treat members of the same class (minus
one, all other integers) differently.
Would there be any gain in clarity or maintainability
if these ifs were somehow eliminated?  Certainly NOT.

Quite the reverse, in fact.
input2 withIndexDo: [ :element :index | |action| 
action:= SantaAction getActionFor: element. floor := action  doMovementFor: floor .
self hasReachedBasement 
ifTrue: [^ index]].
    ^ '-2'.

There is only one word for this: obfuscated.
I was initially puzzled by your returning -2
instead of the conventional 0 if the basement
is not reached, and then *deeply confused* by
the fact that you are returning a *string* in
this case.

Looking at your code, I was further confused
by variables called 'aSymbol' whose value is
always and only a Character, never a Symbol.
And if I am wrong that
IllegalMoveSanta class>> canHandleInput:
in Smalltalk is to return 0 when something is
      
approach



We want to find the first place where something
becomes true.  There are again at least to approaches. 

On Wed, 28 Nov 2018 at 05:41, Roelof Wobben <[hidden email]> wrote:
Hello,

Yesterday I had a talk with luc frabresse about using if then.
He said if I understand it right, Its the best to not using a if then or
a ifTrue/ifFalse.

Can anyone help me figure out how to rewrite this project so I will not
use the ifTrue in the basement function.

my code so far can be found here : https://github.com/RoelofWobben/AOC2015

Roelof