Terseness VS Elegance
One of the factors used by many programmers in the analysis of a program from quality, design and understandability perspective, is
LOC (Lines Of Code) and sometimes that’s taken to another level - specially in the functional programming world and the analysis of each function individually - to
NOW (Number Of Words)!
I understand those metrics and they make sense to a certain degree for sure. One of the many cool things that you experience when you re-write an imperative program in the functional way, is the massive reduction in the number of lines. And we owe that to the WholeMeal Programming approach which is the essence of functional programming. Solving a problem in a general way instead of getting caught in the middle of quite irrelevant details. The best and most famous example of it I guess is Iteration VS Map. Something like:
Can be written in
Haskell as following:
That’s a huge win and you don’t need to get into shenanigans of iteration over a collection. This particular operation is so common in programming that map is almost part of ANY programming language these days. (even Java 8 added that and it’s been in Google Guava library for quite some time)
So far so good. But sometimes having less number of lines and words does NOT necessarily mean you have a better and more elegant code. We’ll demonstrate that with a very simple example in
Imagine you are writing a simple monthly payment tracking application. Obviously in the design of such an application one of the data-types would be Payment:
Also a common feature/capability of this application should be giving you the total amount of money you’ve paid so far. That can be done with a function like the following:
That’s a very terse and succinct implementation for that function! Thanks to cool features of Haskell, spceially in this case the
math-equation-like-parameter-elimination which even allows us to get rid of
payments argument from both sides of the function equation.
NOTE that the same thing would be more verbose even in terse languages like Ruby or Python, let alone a language like Java.
Abstraction is Missing
When I look at that code, it feels like something is missing. The understanding of the data type Payment is not complete in that implementation IMHO. One of the basic operations that you need to be able to perform on multiple Payments in this application, is adding them together. So a better solution is for the data type to provide that capability instead of us reaching into the belly of a Payment and grab what we need in order to perform that operation. That seems like a much more elegant design and doesn’t smell like the inside of a Payment’s stomach at all cause everything would be handled by the Payment and we’ll get back what we need instead of opening something up and mess around with what’s inside of it.
Monoid to the Rescue
If you’re famiiar with Monoid typeclass in Haskell, you would immeidately realize that it’s a perfect match for such requirements. So we need to make the Payment data type an instance of a Monoid:
You can read about Monoids in detail but the main thing you need to know about those functions implemented above are:
- mappend is an ASSOCIATIVE BINARY operation regarding the data type
- mempty is an identity value regarding the mappend (e.g 0 in mathematical ADD)
Now that Payment is a Monoid we can re-write our little
totalPayment function as following:
It’s still pretty terse (one WORD more than previous version if that metric is important to you). BUT, IMHO this version is much more elegant and abstracts away a detail which was involved in the previous implementation. Before we were reaching into EVERY single Payment and got its value and then sum them up. Now we’re ADDing Payments themselves and they know how to take care of that internally and finally we just grab the value out of the result payment.
Elegance over Terseness
If you can improve your design and your codebase with a nice abstraction like the one we did in our example, by all means go ahead and take care of it. We added few more lines of code (instantiation of a Monoid) and more words (our function implementation) but at the end of the day we ended up with a more elegant, abstract and understandable design and codebase. And that is much more valuable than the number of lines and words. So if you have to trade-off between
Elegance I’m sure you know that you should favor
Elegance EVERY SINGLE time!
Ok, I stop preaching at this point and I hope that was interesting. Happy Hacking :)
FIRST Nicola in the comments mentioned the usage of
mconcat in the implementation of
totalPayment which is both terser and more elegant. I totally forgot about that and the funny part is, what I wrote in the above code is the default implementation of
mconcat. So the
foldr mappend mempty can be replaced with its equal value which is
SECOND Christian hinted at a quite better implementation of
squareNumbers function in our first example which is leveraging
curried functions so we can have: