If statements considered harmful(blog.deprogramandis.co.uk)
blog.deprogramandis.co.uk
If statements considered harmful
https://blog.deprogramandis.co.uk/2013/03/20/if-statements-considered-harmful-or-gotos-evil-twin-or-how-to-achieve-coding-happiness-using-null-objects/
14 comments
I'm really waiting for the ultimate "Programming is considered harmful" article.
This is a case of employing OO to circumvent a problem in OO languages. There's two potential problems with if statements: 1) non-locality 2) fragility.
1) can often be dealt with by changing the code, or parameterising. But in general, you want to be able to specify your own interface and specify how specific classes handle it. The article misses out one important detail, because the average OO language doesn't handle it: you might want to do this to someone else's class 2) You can make fragile code in any language, but the absence of pattern matching with totality checking makes it much worse.
Of course, there's a third problem that's implicit throughout the article: the inability to verify something's not null through the type checker.
The ideas in the article are good, but they're patterns for circumventing problems in your programming language. There's languages with no OO whatsoever that avoid the problems.
1) can often be dealt with by changing the code, or parameterising. But in general, you want to be able to specify your own interface and specify how specific classes handle it. The article misses out one important detail, because the average OO language doesn't handle it: you might want to do this to someone else's class 2) You can make fragile code in any language, but the absence of pattern matching with totality checking makes it much worse.
Of course, there's a third problem that's implicit throughout the article: the inability to verify something's not null through the type checker.
The ideas in the article are good, but they're patterns for circumventing problems in your programming language. There's languages with no OO whatsoever that avoid the problems.
The problem identified has nothing to do with OO. Look at this code:
Conversely, this is only a "problem" if `logFile.write()` results in SIGSEGV or NullPointerException or whatever. But some OO languages (Smalltalk, Objective-C, others?) dodge this by making null a message black hole. Confusing, but terribly convenient.
One last point: notions of "pattern matching with totality checking" or "verify something's not null through the type system" presupposes exhaustive knowledge of the environment in which your code will run. Maybe that's correct: google.exe on Google owned hardware in Google-owned data centers will behave very precisely.
But sometimes it's not correct. Your JavaScript code is running in a yet-to-be-written browser on an ancient version of some OS on random hardware. Your code's typechecking environment is unrelated to its runtime environment. Totality checking requires predicting the future. In this highly dynamic universe, languages like Haskell have no good solutions.
logFile.write(message);
We might as well replace `logFile.write` with `fprintf(logFile...)`, and get the same result in C. So we don't need OO to experience this pain.Conversely, this is only a "problem" if `logFile.write()` results in SIGSEGV or NullPointerException or whatever. But some OO languages (Smalltalk, Objective-C, others?) dodge this by making null a message black hole. Confusing, but terribly convenient.
One last point: notions of "pattern matching with totality checking" or "verify something's not null through the type system" presupposes exhaustive knowledge of the environment in which your code will run. Maybe that's correct: google.exe on Google owned hardware in Google-owned data centers will behave very precisely.
But sometimes it's not correct. Your JavaScript code is running in a yet-to-be-written browser on an ancient version of some OS on random hardware. Your code's typechecking environment is unrelated to its runtime environment. Totality checking requires predicting the future. In this highly dynamic universe, languages like Haskell have no good solutions.
You're right, you could certainly experience the pain in a non-OO language that silently allows invalid values to creep through the system. However, it's an error _every_ OO language makes. Smalltalk, Objective-C and Clojure dodge it, but they don't dodge it for long: the nulls can now propagate further through your program, making it harder to track your problem down.
I'm not sure your last point buys us anything. I mean, any runtime environment can have bugs, and any future system can have cases you haven't handled. This will break your OO code just as fast as pattern-matching. Because OO dispatch is really just a special case of more general mechanisms.
Have a chat to some purescript guys about when they have trouble with JavaScript nulls. I bet the answer is "when we interface with raw JavaScript code and don't define our types correctly".
I'm not sure your last point buys us anything. I mean, any runtime environment can have bugs, and any future system can have cases you haven't handled. This will break your OO code just as fast as pattern-matching. Because OO dispatch is really just a special case of more general mechanisms.
Have a chat to some purescript guys about when they have trouble with JavaScript nulls. I bet the answer is "when we interface with raw JavaScript code and don't define our types correctly".
Care to go into some details/examples of your last sentence?
I think this article is a pretty good introduction to when exhaustive pattern matching is desirable:
http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipatt...
http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipatt...
In this case, I prefer FP-style destructured pattern matching (e.g. https://doc.rust-lang.org/book/patterns.html#destructuring) to OOP-style method overloading (FTA). In the FP style, the compiler can make sure you handle all possibilities.
It's worth noting you can implement something similar in OOP languages using Church encoding: http://blog.higher-order.com/blog/2009/08/21/structural-patt...
tl;dr: OOP is sometimes good.
polymorphism is very good, but not handcuffed to OOP.
Clojure protocols, Haskell typeclasses (and many other examples I'm sure) provide the same functionality "a la carte" without the rest of OOP's features coming along for the ride (to paraphrase Rich Hickey and Stuart Halloway). More eloquently explained details in this talk at around the 21 min mark [0].
[0] https://www.youtube.com/watch?v=cidchWg74Y4
Clojure protocols, Haskell typeclasses (and many other examples I'm sure) provide the same functionality "a la carte" without the rest of OOP's features coming along for the ride (to paraphrase Rich Hickey and Stuart Halloway). More eloquently explained details in this talk at around the 21 min mark [0].
[0] https://www.youtube.com/watch?v=cidchWg74Y4
One big missing feature in typeclasses is overriding. You can't "call into super."
A concrete example: you make a new type in Haskell, and you derive Show so it can be printed. But the default output isn't quite right - you want to add something to it. In an OO language you might "call into super" and append to the string. But in Haskell this isn't possible: you either get the default implementation, or you do it all from scratch.
More generally, typeclass default implementations are a lame replacement for overriding, and existentials are anemic compared to dynamic binding. This pushes Haskell interfaces towards rigid, static designs. That's fine if that's what you want, but let's not pretend it's equivalent in power to OO polymorphism.
A concrete example: you make a new type in Haskell, and you derive Show so it can be printed. But the default output isn't quite right - you want to add something to it. In an OO language you might "call into super" and append to the string. But in Haskell this isn't possible: you either get the default implementation, or you do it all from scratch.
More generally, typeclass default implementations are a lame replacement for overriding, and existentials are anemic compared to dynamic binding. This pushes Haskell interfaces towards rigid, static designs. That's fine if that's what you want, but let's not pretend it's equivalent in power to OO polymorphism.
that sounds a lot like sharing code by inheritance as opposed to composition. which is unrelated to dispatching by the type of the first (or implicit) argument.
i personally would prefer to code to interfaces and keep hierarchy separate from behaviour
i personally would prefer to code to interfaces and keep hierarchy separate from behaviour
perhaps I haven't felt your pain of rigid static designs because i mostly work in clojure, ymmv
If I find repeating if statements throughout my code I put them in a method, end of story. Pseudopython:
I don't understand why the author would even bother with more than this, given his example. Also note that I used two return (disguised goto's) as guard statements to check the conditions before I do anything.