May 11, 2008

Law of Averages

After reading a blog entry from Raganwald's delicious feed called Perfecting OO's Small Classes and Short Methods, I have some words to say. The author lists a number of exercises to force programmers out of a procedural groove and into a more object-oriented one.

This makes me wonder, is an object-oriented groove really a good one? In Java, you tend to write way more code than you really have to. Example:
//input a string from the keyboard
String input;
try{
BufferedReader in = new BufferedReader(new FileReader(System.in));

input = in.readLine();
}catch(Exception e){
}
Compare to Ruby:
input = gets
or even C++:
char input[1024];
cin.getline(input, 1024);
Something about the enforced "object-oriented" approach seems to me like there is more work to it than in a less object-oriented fashion. Don't get me wrong, using object-oriented techniques really saves you effort in many situations, but it is a diminishing return. If you try to program everything with an object-oriented style, you'll end up doing more work than you have to.

Let's move over to functional programming. This seems to be the latest craze among geeks nowadays, which means in 10 years it'll probably be big business. Best for young programmers like myself to get into it now.
It has some great advantages. For example, using map or reduce (in Ruby, inject) you can write some really good one liners:
#sum up the prices of our order
sum = shopping_cart.inject(0) { |total, item|
total + (item.price * item.quantity)
}
compare it to PHP:
$sum = 0;
foreach ($shopping_cart as $item){
$sum += $item->price * $item->quantity;
}
Not a lot longer, but shows a very different style of programming. For a trivial example there isn't a huge difference, but for more complex things the functional approach can be a lot nicer. Plus it scales really well, which probably explains why functional programming is a big thing nowadays, since scaling to multi-processing seems to be another big thing nowadays.

Functional programming also has it's disadvantages. Look back at the Ruby vs. PHP example. If you had never heard of inject/reduce before, would you have any clue what that did? Now compare it to this:

(define (roots a b c)
(if (< (* b b) (* 4 a c))
#f
(list (/ (+ (- b)
(sqrt (- (* b b) (* 4 a c))))
(* 2 a))
(/ (- (- b)
(sqrt (- (* b b) (* 4 a c))))
(* 2 a)))))
Do you have any clue what this is? To the educated eye (or the intelligent eye) you can probably figure out that this is the quadratic formula. Written in Scheme. While Scheme has a beauty in its simplicity, it is rather difficult to read.

Note that other functional languages like ML or Haskell are a lot easier to read than Scheme, but they have problems too. Attempt to learn them and you will probably see. Personally I don't see the appeal of Haskell with its "pure" functional approach. I'd say that mutable values are a great way to get things done, you don't have to spend time thinking "OK, how will I do this without changing any values, and still have the function tail recursive so that I don't run out of memory?"

What I'm thinking is take the good parts of everything. There are many good things about object-oriented programming (ever write a GUI app?), there are also many good things about other paradigms. Knowing the different paradigms will make you a better programmer, but using them alone will probably make you worse. It's good to see that the designers behind languages like Scala seem to have understood this. Now if I could just get Scala to work...

6 comments:

Jake McArthur said...

Note that other functional languages like ML or Haskell are a lot easier to read than Scheme, but they have problems too. You could not directly translate the above example to ML or Haskell since they are statically-typed. The above example either returns a boolean value (#f for false), or a list, depending on the situation.

Not really true.

roots a b c | b*b < 4*a*c = Nothing
| otherwise = Just ( (-b + sqrt (b*b - 4*a*c)) / 2*a
, (-b - sqrt (b*b - 4*a*c)) / 2*a
)

It seems that blogger will not allow me format the code properly. Sorry about that. Mentally align the pipes, and also align the outermost parens and the comma and you will see what I intended it to be.

Anyway, the above clearly doesn't return either a bool or a list. Rather, it returns a Maybe (n, n), where n is whatever Floating type desired. I could have more literally copied the Scheme version, but something of type Either Bool [n] just doesn't properly convey the idea of what the function does to me.

IllegalCharacter said...

Yes, excuse my ignorant self ;) I was simply saying from my limited experience that you can't do that, but apparently you can. I'll fix that.

Anyway my point was to say that functional languages have their problems and that it is ultimately better to take parts of several paradigms and combine them than to strive for some sort of "pure" paradigm. This way you don't restrict a programmer into a particular way of thinking (usually unhealthy to be that way).

Jake McArthur said...

"Attempt to learn them and you will probably see."

As somebody who obviously did this, I respectfully disagree. Haskell is now my favorite programming language. There is nothing wrong with multiparadigm, but purity is something which, once corrupted by impure features, can never be recovered, meaning that multiparadigm languages will never ever be able to reap all the benefits of purity that Haskell can.

IllegalCharacter said...

I will put Haskell down on my "languages-to-learn" list. I want to see the benefits of purity that you speak of.

I think that the words you choose to describe these features like "corrupt" and "impure" tend to show your opinion much more than fact. I'll have to verify this myself, but I don't think that having things like side-effects cripples your ability to program. I would say more that restricting the programmer from using these things is crippling them from finding an optimal solution to the problem they are trying to solve, should that problem be optimally solved by a restricted means.

Chris H said...

Actually the correct way in C++ is cin>>whatever. Moreover if you need to convert strings to int, or ints to strings, try boost::lexical_cast{std::String}(int). (Thoose { are the tags, since I can't type them in here) The point is it's just _JAVA_ that forces you to do things in an OO way. C++ is flexable, reading from stdin shouldn't have to be object oriented, so it isn't. (unless you really want it to be) i'm posting this because I think it's unfair how you lumped C++ in with java.

IllegalCharacter said...

Actually cin>>whatever only works if they don't input any spaces. Otherwise you have to use getline().

I didn't lump C++ in with Java, it's just C++ is a lot closer to Java than any of the other languages I mentioned (Ruby, PHP, Scheme, ML, Haskell) so it might look like I lumped it in there.

I completely agree with you on how Java forces you into an "object-oriented" mindset. C++ is great since it does give you the freedom to make your own choices, although the need for speed does get in the way sometimes. Oh well.