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 |
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 On 11/27/18 8:40 AM, Roelof Wobben
wrote:
Hello, |
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, |
> 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 > > |
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 |
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
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, |
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:
|
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:
|
Free forum by Nabble | Edit this page |