Custom Search

Thursday, May 1, 2014

How pythonic is Haskell?

Introduction

I've recently switched to Haskell from Python, and I think it might be interesting to look at how far from being Pythonic Haskell actually is - how much of a change did I actually make?

Why Haskell?

The reason for changing was - having spent half a decade working on large - well, medium-sized these days - concurrent programs, which produce the nastiest bugs I've run into because they are seldom reproducible, the primary source of those bugs seemed to be errors in dealing with shared data objects. Getting the locking wrong, or simply failing to lock something that was shared are often the cause. A number of solutions to the first exist, but the latter seemed to be a language issue. Haskell provided the best solution I found - that a data object may be mutated is part of it's type, so failure to properly deal with them in a concurrent environment can be caught by the compiler. If that type system sounds interesting, you might want to read this article that covers its features.

The Zen of Python

It's generally agreed upon that Tim Peters (with help from others) captured the philosophy behind Python and it's libraries in a document called The Zen of Python. You can read this by running the command python -m this. So I'm going to go through each item, and see how well Haskell adheres to that element of the Python philosophy.
Scored on a scale of 1 (complete fail) to 11 (better than Python), with Python scoring a 10. That the scale goes to 11 tells you exactly how serious I am.

Beautiful is better than ugly.

They say "beauty is in the eye of the beholder, but ugly goes clean to the bone". So I want to delay this one until I've talked a bit more about Haskell to provide a basis for comparison.

Explicit is better than implicit: 9

I'm tempted to rate this as better than python since mutability is made explicit, but the do statement - with it's implicit parameters wrapping statements - detracts a bit. They are just syntactic sugar, and can be translated back to the explicit form in your head, so it's not a major problem. But they are everywhere!

Simple is better than complex: 10

Haskell programmers prefer pure code because it is simple. Being lazy is much simpler than yield.

Complex is better than complicated: 11

Monads very simple things that have very complex effects on the language and programs. They replace a number of things that are complicated in Python.

Flat is better than nested: 11

Haskell's module system is very similar to Pythons. At the language level, Haskell provides the let and where statements, which are a recurring request for Python, because they make managing the top-level namespace easier.

Sparse is better than dense: 10

Split decisions here. While the language encourages short functions - which leads to sparseness - it also encourages long sequences of combinators or filters, which can lead to dense code in the function.

Readability counts.

Also deferred to the next section.

Special cases aren't special enough to break the rules: 11

Haskell has fewer special cases than any other language I've run into. Maybe I just need to look harder?

Although practicality beats purity: 6

See the previous note. They didn't even special case numeric conversions! Meaning you either have to convert integers to a fractional type to involve them in a division, or write functions that are context sensitive to what they return. Ok, the latter isn't hard, but not enough of the builtins do it.

Errors should never pass silently: 9

The language doesn't eat errors, and generally makes doing so harder than in python. However, there are multiple ways of handling errors, leading to some confusion.

Unless explicitly silenced: 10

Yes, you can explicitly silence errors.

In the face of ambiguity, refuse the temptation to guess: 11

The type system pretty much disallows ambiguity. This carries through to much of the rest of the language. There are even language extensions that allow the programmer to explicitly declare some cases as "not ambiguous."

There should be one -- and preferably only one -- obvious way to do it: 2

Many functions and operators have multiple names, just to start with. It's not at all uncommon for combinator libraries to have multiple similar combinators, allowing the exact same process to be specified in a combinatorial number of ways.

Although that way may not be obvious at first unless you're Dutch. NA

Not being Dutch, I can't properly evaluate this one.

Now is better than never: 7

There are a number of areas that still need enterprise-quality libraries.

Although never is often better than right now: 7

The Haskell Platform - Haskell's answer to "Batteries Included" shows signs of some things being done right now that might better have been done never.

If the implementation is hard to explain, it's a bad idea: 9

Most implementations are easy to explain as long as you keep in mind that a monad is a monoid in the category of endofunctors.
In other words, Haskell programs make heavy use of monads (the language even has special syntax for them), which aren't available in commonly used languages. It's not unusual to wind up with stacks of them, so there are libraries for working with those stacks. Many implementations are easy to explain once you get those. See the simple vs. complex koan.

If the implementation is easy to explain, it may be a good idea. 9

See above.

Namespaces are one honking great idea -- let's do more of those! 6

There are no objects. While name spaces nest in let and where functions, you don't have methods. If you want to use two data types that have what would be a method name in common, you either don't use it, or arrange to refer to it by a different (qualified for people who know Haskell) name.

Beautiful is better than ugly.

And of course, "Readability counts."
The first time I saw a Haskell program, my reaction was "That's uglier than Perl". It turns out that Haskell lets programmers define operators. And they do.
While this might be a disaster with other languages, the functional nature of Haskell means that a common programming paradigm is a function that takes two functions as arguments and returns a functions that combines them in some way - a combinator. It's much easier to read - or u such things as expressions than as actual function invocations.
Even Python libraries recognize this. Things like XPath and regular expressions are generally done with strings holding expressions that are fed to functions to be parsed rather than being broken out into functions that are then combined by other functions. Haskell allows such things to be expressed in Haskell, and hence compiled and checked - including for type - by the compiler.
Of course, this means that when you learn a new library, you may have to learn a new set of operators as well as functions. And since some people want to right code left-to-right and others right-to-left, it's not at all uncommon to have three versions of a function: one that applies the left-hand operand first, one that applies the right-hand operand first, and a function name for those who want that.
The end result is code that can be beautiful and very readable - once you know the operators involved. But it in no way resembles the "executable pseudo-code" that Python has been described as.
So, I'm going to give "beautiful is better than ugly" an 8, because it encourages beautiful code nearly as well as Python does, but doesn't do much to discourage ugly code.
On the other hand, "readability counts" gets a 3, because operators tend to provide much less information than function names.

Summary

Adding them up, we get that Haskell scores an 8.2. That actually feels about right to me. Of course, you're free to disagree. In particular, some of the koans are subject to interpretation, and you may or may not agree with my interpretation, much less my scoring of Haskell on them.
If you disagree strongly, please, publish your own scoring. Or even your scoring of a third language!

And the other way

Haskell has a motto instead of a Zen - "Avoid success at all costs." Python pretty much fails at that completely.