Using Polymorphism instead of conditionals when programming
theshyam.com
theshyam.com
There's a time and place for good OO, but knowing when not to apply it seems like an equally useful skill as knowing when to apply it.
The disadvantage of poly here is more code and more classes. Yes, you can add a new operator without modifying the node class - but now you must modify the tree-populating code instead.
- You have reduced the cyclomatic complexity of each class, which makes the code less fragile and easier to unit test.
- When adding a new operator, you are now forced to implement everything it needs to work. If you don't define a method, it won't compile. This is not the case with a long list of if/else statements.
- "You still need a nest of conditionals when you populate the tree..." This is true, but now all the conditionals are in one place, rather than two (in their example, toString and evaluate). Again, this will make testing easier.
I try to take this as far as I can. For instance, when writing Servlet code in Java, I define all my request parameter names in an enum, so that I can never misspell any lest the compiler tell me.
I don't actually use inheritance, but composition. Who contains who depends on the grammar. The expression 2 + 3 * 5 becomes (using a pseudo-object shorthand) Add(2, Mult(3, 5)).
It looks something like this: http://github.com/scotts/cellgen/blob/master/src/math_exprs.... There is an interface that all of the classes share, I just haven't bothered to abstract it out. (Partially because it often changes, and I've never need to refer to that interface explicitly.)
I get his point, of course, it's just that the solution he's looking for isn't the most natural way for me to solve the general problem.
data Op a = Constant a | Add (Op a) (Op a) | Multiply (Op a) (Op a)
deriving (Show)
evaluate :: Num a => Op a -> a
evaluate (Constant x) = x
evaluate (Add l r) = evaluate l + evaluate r
evaluate (Multiply l r) = evaluate l * evaluate r (defgeneric evaluate (op))
(defmethod evaluate ((op add-op)) ...)
(defmethod evaluate ((op mult-op)) ...)
(defmethod evaluate ((op const-op)) ...)
This is the same as class AddOp { method evaluate { ... }}
...
and so on.In either case, it eventually compiles down to something like:
(defun evaluate (op)
(typecase op (add-op <add>)
(mult-op <mult>)
(const-op <const>))
which is just an if statement with some sugar on top.In the end, pattern matching and polymorhism are just syntax sugar on top of "if". But it's syntax sugar that makes the code very easy to read and write correctly. (The advantage is that you write a method for each type, which means one lexical block only knows about the specifics of the operation is is performing; no need for Add to know about Mulitply, for example.)
Polymorphism is the core of OOP, which consequently means that Haskell is OO.
OK, irrelevant. We are not discussing runtime implementation, we are discussing language semantics. A table lookup and a cascade of if statements are semantically the same. Value in, result out.
Polymorphism is the core of OOP, which consequently means that Haskell is OO.
You're going to have to explain this with more than one statement. "OOP" is a meaningless expression. "Haskell is OO" is similarly meaningless.
My Haskell example shows that functions can be polymorphic over values. OOP is polymorphic over types. Haskell typeclasses provide polymorphism over classes of types.
Not the same. But similar expressive power is possible either way.
My Haskell knowledge is superficial so i'll keep my mouth shut about the various kinds of polymorphism you describe.
Vtables means just one indirection thus no more than one more memory access. Pattern matching is like parsing.
eval(const(X), X).
eval(add(L, R), S) :- eval(L, LV), eval(R, RV), S is LV + RV.
eval(mul(L, R), P) :- eval(L, LV), eval(R, RV), P is LV * RV.
?- eval(const(6), Z).
Z = 6.
?- eval(mul(const(3), add(const(71), const(9))), Z).
Z = 240.Java uses types for pattern matching since that's all the language allows pattern matching on. (Common Lisp is similar, but also has eql qualifiers.)
The type system can be useful for more complex cases. Eventually you might have a good reason to represent each operation with its own type. In this case, typeclasses can remove redundant code. (For example, if you write a lazy evaluator for each type of operation, you can get the eager evaluator "for free", implemented in terms of the lazy evaluation in the typeclass definition. etc., etc. I am too tired to decide how this would happen in Java, but my gut instinct is "not cleanly".)
newtype Node a = (a, string)
op :: (a -> b -> c) -> string -> node a -> Node b -> Node c
op (<>) str left right = (fst left <> fst right,
"(" ++ snd left ++ " " ++ str ++ " " ++ snd right ++ ")")
plus = op (+) "+"
mult = op (*) "*"
minus = op (-) "-"
constant x = (x, show x)
I don't even need eval: Haskell is non strict!I suggest "Unconditional Programming"
class TrueClass:
def if(then, else):
then.call()
class FalseClass:
def if(then,else):
else.call()
condition = true
condition.if({print "true"}, {print "false"}) condition = x < y
and a compiler would create a conditional jump.if(condition) { print "true" } else { print "false" }
What I meant was that if you had code that ran to look up / generate those blocks, rather than just a literal block, it would always process both (even though only one is needed).