Lucio's Rambles

Elegant Means Unreadable

As part of my last year of uni we had a mandatory class teaching us Functional Programming, or more specifically, teaching us the programming language Haskell. Haskell is a very interesting language, being structured so very differently from Object Oriented Languages and including many neat features I never knew I so desperately wanted, but it's also a very unpopular language, having been described as "guaranteed to have no side effects [...] because no one will ever run it[.]"

People who write in Haskell (or atleast, our teacher said that people who write in Haskell) pride themselves on writing simple, elegant code that you can reason with easily. Instead of having all that bloat that other programming languages have with semicolons, parentheses, and "ceremonial code" that you have to write to get things set up, the code in Haskell is expected to just work while being as short as possible to make it easy for the readers to parse through.

For example - let's say that I wanted to make a function that took a number, added two to it, and then squared and returned the result. If I wanted to do such a thing in C#1, I'd write it like this:

public static int AddTwoAndSquare(int num) {
    return Math.Pow((num + 2), 2);
}

This code could be argued to be very bloated for such a simple operation. The square brackets, semicolon, return, and static are all kind of unnecessary when you read the code and it's just there to tell the compiler itself what's going on. Additionally, Math.Pow is a little opaque - which number is being put to the power of which? So instead, if we wanted to write this in Haskell, it could look like:

addTwoAndSquare num = (num + 2) ** 2

Much shorter and more readable; you can literally read it left to right and get the whole picture quickly. But what if we wanted to go further? It's kind of obvious that num is the item we're operating on, so can we remove that too? Well, yes! In Haskell, (to make matters short,) if there's a variable that's mentioned at the very end of both a function declaration and the function itself, you can remove both instances and have perfectly functional code. Meaning that this:

plusTwo num = 2 + num

Is equivalent to:

plusTwo = 2 + 

Which could be convenient. So, let's move our num to the end of the function and chop it off!

addTwoAndSquare num = (num + 2) ** 2

addTwoAndSquare num = (2 + num) ** 2

addTwoAndSquare num = (**) (2 + num) 2

addTwoAndSquare num = (flip (**) 2) . (2 + num)

addTwoAndSquare = (flip (**) 2) . (2 +)

addTwoAndSquare = (flip (**) 2) . (+) 2

There we go2! Lovely, elegant, and readable. Well, mostly readable. Kind of readable. Supposedly readable. It's readable if you know the language. Even then it'll take you a minute to parse through it.

You can already see the issue caused by trying to make even this very simple function more "elegant", but this gets progressively worse the more complex the functions become. Sure, if your function does exactly one thing and abstracts the rest to something else, it's very readable and straightforward, but that's not something to brag about because delegating to other functions is readable in any language that wasn't invented by masochists.

When we got to my final exam, I had no idea what was written on the page anymore. Seriously, here's an actual function taken from one of my finals, and if you can tell me what this does just by the function description (which Haskell supposedly prides itself on), I am gonna give you whatever you want.

foo initialMap =
uncurry go . flip execState (S.empty , initialMap) . traverse (uncurry iteration)
where
go :: Set String -> Map String Int -> Either (Set String) (Map String Int)
go s m = if null s then Right m else Left s
iteration s e =
modify $ \ (missing , values) -> case evaluate values e of
Left s -> (missing <> s, values)
Right i -> (missing , M.insert s i values)

Other than the initial function name, I have changed nothing about this solution. It doesn't come with an explanation either. "The code is self-documenting." Self-documenting, my ass.

I recently stumbled upon a post from qntm titled it's probably time to stop recommending Clean Code, and it goes over a book named Clean Code that supposedly revels in showing you how to write good, clean, elegant code. While the book has generally good advice, qntm notes that it also sneaks in extremely counterproductive advice, and the code examples it gives are downright unreadable. This adds to a trend I've seen with people who try to write code that's pretty rather than readable - incomprehensibility is almost deemed a virtue, and code is treated more like poetry where the shape of the code is just as important as the words it contains.

Code is first and foremost meant to be read by humans. Unless you're pulling out some demonic C code like the fast inverse square root, the code's speed is second in importance to making sure the code you write is editable and maintainable by people in the future. Even if you're writing a small project by yourself, at some point you'll have to debug a function you wrote centuries ago, and you'll have to understand what the hell the moron who wrote this code (you) was smoking when they decided to write a function that makes no sense on first readthrough.

Stop trying to write "short, self-documenting" code. Assume whoever is reading your code is an actual monkey who picked up a laptop for the first time, and make sure every sneeze in the wrong direction is documented. It'll help you down the line, and it'll help you in the moment when you have to reread what you wrote and save yourself from accidentally breaking production code by doing a + 1 rather than a -1.

This has happened more than once.

  1. I am aware that C# prefers verbosity over elegance so this is a bit of an unfair example, but it gets the point across.

  2. I did not check if this is valid code. If what I wrote ends up to be code that does not compile or runs incorrectly, you are welcome to send any comments and complaints directly to the local landfill.

#tech