Why You’ll Probably Never Use Haskell in Production

If you’ve ever chatted with a Haskell programmer, you’ll probably hear that using Haskell in production is one of the greatest joys in life. But the grim reality is that Haskell just doesn’t quite work out in most corporate environments. As a pure functional programming language that was born from the depths of academia, there are several issues that make most developers hesitant to adopt Haskell.

So without further-a-do, I’m going to explain to you why you’ll probably never use Haskell in production.

 


Haskell isn’t a popular programming language


Unfortunately, the reality of Haskell is that very few people know how to code in Haskell, and even fewer are good enough to use it to the same level of productivity as the average Python or Java developer. This causes two big issues.

  1. Hiring a Haskell developer is much harder than hiring a Java, C++, or Python developer.
  2. Teams are unlikely to switch to Haskell because the majority of team members probably won’t know Haskell

Because of these two issues, it’s nearly impossible to use Haskell in production without having a team that is already fluent in Haskell. However, because it’s much harder to hire a Haskell developer than a Java/Python developer, it also becomes highly unlikely that a team of Haskellers will ever be formed.

One common argument is that if Haskell is a perfect fit for your business requirements, then you can just teach your team Haskell. After all, developers are capable of learning new technology stacks on the fly, so there’s no need for everyone to already know Haskell.

While this sounds good on paper, it’s not actually possible for developers to just “learn” Haskell, because…

 


Haskell has a massive learning curve


Any developer worth their salt should be able to learn a new OOP programming language in under two weeks. If you already know a couple of OOP languages, then learning another OOP language is incredibly easy, because all of your knowledge transfers over.

But Haskell doesn’t work in the same way, because it’s a completely different paradigm, and sets itself apart even from other FP languages. Its learning curve is by far the steepest out of any programming language I’ve worked with. Not to mention, it’s probably one of the few programming languages in the world where people will endorse reading and sharing research papers to learn Haskell.

That’s not all though, because when it comes to Haskell, your programming experience works against you. It doesn’t take two weeks to learn Haskell. It takes months to learn Haskell, and likely several more to be productive in it. The issue is that since you already have certain expectations of what programming is about, you’ll have preconceptions on how things might be done in Haskell. And all of those preconceptions will be wrong.

For example, in most OOP languages (eg; Java and C#), a function that returns an integer doesn’t really return an integer. It returns an integer or null, and this is a problem in Haskell. In Haskell, if your value has a chance of returning “null”, then you need to wrap it with the Maybe monad.

But why do we even need monads? In an OOP language, everyone got along just fine without them (mostly by doing null checks everywhere).  So why do we need monads in Haskell if we never had to use one in any other programming language?

And what is a monad anyway? Unfortunately, monads are notoriously hard to explain. They’re so hard, in fact, that there’s an entire meme about monad tutorials. Some people will tell you that monads are burritos, others will say monads are boxes, and really, none of them will do much to help you understand monads. You’ll have to work with monads in order to understand them, and when you do understand them, you’ll wonder why you were ever confused about them in the first place. 

When it comes to Haskell, monads are essentially brick walls for beginners. Most people will simply quit when they realize that almost every Haskell library in the world requires monads. But suppose someone does get over that gap. Understanding monads in general, and understanding monads in a standard Haskell library are two completely separate things, and so we see our third problem.

 


Haskell has very few mature and documented libraries


Technically, I’m cheating by listing two points in one, but they’re both very relevant to the issue at hand.

Firstly, when it comes to libraries, Haskell only has a handful of good libraries. These are the ones that almost everyone knows — Parsec, Yesod, pandocaeson, etc. These libraries have good documentation, in fact, some of them are spectacular, like how Yesod literally has a book for its documentation.

But those are just the mature and well-known libraries. But what if you want to use some relatively less well-known libraries? Tough luck, because those libraries almost never have any documentation.

In fact, when it comes to documentation, a lot of experienced Haskellers will tout that the only documentation they need are the function names and type signatures. And it’s true, you can usually figure out a lot from those two, and the Theorems for Free paper is a great read if you’re looking to learn more.

But is it really enough? For most developers, we want to see example code. For popular libraries, sure, there are thousands of examples, but for a smaller library? You’d be lucky for them to even have a useful README page. Working with poor, undocumented Haskell libraries is something you need to experience for yourself to understand why this is such a deal breaker.

All-in-all, Haskell’s poor library ecosystem makes it incredibly risky for anyone to use Haskell as a true “general purpose” programming language, because Haskell only has mature libraries for a small handful of fields. If your problem domain isn’t completely solved by some combination of Haskell’s popular libraries, then you should probably choose a different language.

 


Conclusion


If you’re looking to use a functional programming language in a production environment, Haskell is likely a bad call.

Very few people know Haskell to start with, and its insane learning curve means that it will take a long time before a developer reaches a proficient level with Haskell. Combine this with Haskell’s poor library ecosystem, and any plans for using Haskell in production will likely come to a grinding halt.

For a normal production environment, with a normal team, you are better off using F# or Clojure. Both of these languages have much more gentle learning curves, better documentation, and a huge library ecosystem since they were built with interoperability in mind. F# has complete access to all .NET libraries (and it’s directly supported by Microsoft!), and Clojure runs on the JVM, so you can run any Java library on it. Both of these are much safer choices than Haskell, and if neither of them work out, there’s always the option of using Scala (which also runs on the JVM).

But maybe you’re one of the lucky few who are working on a corporate Haskell project, and there are several examples, like how Facebook created Haxl and Sigma for spam detection.

But these are the exceptions, rather than the rule. Most of the time, people just want to get things done, and unless the stars align with you having exactly the right team with exactly the right problem, Haskell is almost never going to provide a solution that is the fastest, safest, nor the cheapest.

Everything in Haskell Is a Thunk

There are a lot of misconceptions about Haskell.

Some people think everything in Haskell is a function. Others think that Haskell is just an implementation of Church’s lambda calculus. But both of these are false.

In reality, everything in Haskell is a thunk.

A thunk is a value that has not been evaluated yet. If everything in Haskell is a thunk, then that means that nothing in Haskell is evaluated unless it has to be evaluated.

From a non-functional programmer’s perspective, a thunk may initially seem to be a useless feature. To understand why thunks are useful, consider the following Python code.

x = 5 / 0

Traceback (most recent call last):
  File "", line 1, in 
ZeroDivisionError: division by zero

Unlike Haskell, Python evaluates the results immediately, which means that Python will crash as soon as an error occurs.

On the other hand, Haskell is different. In fact, you can string together dozens of lines of code, all of which contain an error, and no error will appear until one of those lines get evaluated.

let x = 3 `div` 0
let y = x

-- Can even divide x and y!
let z = x `div` y

let stringX = show x

-- Evaluate stringX, which
-- evaluates show x, which evaluates x
-- which finally evaluates 3 `div` 0, causing
-- the error to occur
putStrLn stringX

In this example, no matter what, Haskell will not give an error until something is evaluated. A common misconception is that a function call will always force the results to be evaluated, but that is simply not true.

In the example above, z is assigned the value of x divided by y, and yet there is no error. It is only when the value of stringX is evaluated via putStrLn (which prints a String) that Haskell throws an error.

One way to imagine thunks is to think of every value and function as being wrapped in a box. No one is allowed to peek into the box until the order is given. For all Haskell knows, the box could have anything in it, and Haskell wouldn’t care. As long as there are no type conflicts, Haskell will happily pass the box along.

In the code above, Haskell is fine with dividing two errors (both x and y, when evaluated, will give a division by zero error). However, it is only because x and y are both thunks, and Haskell doesn’t actually know the true values of x and y.

This gives Haskell the benefit of aggressively optimizing code, allowing for massive performance boosts.

Imagine if Haskell had an expensive function, F, that takes in one parameter, takes 10 seconds to run, and returns the evaluated value 5. Say that this same function also existed in Python.

If we were to run F ten times, we would find that Haskell only takes about 10 seconds to finish, while Python would take 100 seconds. Here, Haskell’s functional properties and lazy evaluation allows it to outperform Python.

In Python, functions are allowed to have side-effects. This means that if a Python function is run ten times, with the same input, it can give ten different results. On the other hand, for Haskell, if a function is run ten times, with the same input, it will always give the same result. This means that if there are multiple copies of the same function, called on the same input, Haskell can be certain that those results will all be the same. This is known as referential transparency, and it’s a feature that exists in most functional programming languages.

This property, combined with Haskell’s lazy evaluation, allows Haskell to memoize function calls. Now, if Haskell sees multiple copies of this expensive function called on the exact same input, it will simply evaluate one of the function calls, cache the result, and replace every future function call of F on that same input with the cached result.

What About Infinite Lists?

One consequence of everything being a thunk in Haskell is that Haskell is able to create infinite lists, and pass infinite lists around. In other words, Haskell is able to process and manipulate infinite lists as if they weren’t infinite, because they aren’t. Although the lists are technically infinite, Haskell only takes the elements that it wants.

For example,

let x = [1..]
let y = [1..]

x ++ y
-- concatenate all of x with all of y
-- to infinity
[1, 1, 2, 2, 3, 3, 4, 4, 5, 5 ...]

Here, x is stored to the infinite list from 1 to infinity, and y is stored to the exact same infinite list. Haskell is perfectly fine with storing these infinite data structures, because Haskell does not actually evaluate these structures until it needs to. When they are finally evaluated, they perform exactly like an infinite list — because that is exactly what x and y are.

In Haskell, treating everything as a thunk allows big performance boosts, and also gives the programmer the ability to store infinite data structures. So the next time someone says, “Everything in Haskell is X”, gently remind yourself that unless X is a thunk, they’re most likely wrong.