Why You Should Learn Haskell

Take a look at what makes Haskell such an effective problem solving paradigm.

Even after just one simple example, it should be apparent that functional programming in Haskell is radically different from imperative programming. This, of course, raises the question of one should invest the time to learn such a new paradigm from the ground up. Is it worth it? Definitely! In this section we will look at some reasons why.

Thinking in types

Haskell is quite renowned for its powerful and expressive type system. But what is the big deal about types, anyway?

The greatest advantage is that all type checking happens at compile time – it is a static process that can be carried out without running the program at all. This way, potential bugs don’t make it into the wild.

A famous example of a fatal software fault at runtime is the failed mission of the Mars Climate Orbiter by NASA. In 1998, all communication to the spacecraft was lost as it was approaching its planned orbit around Mars. It was later revealed that part of the software conducted calculations in imperial units when another part required metric units. This caused the failure.

Mistakes like these can be prevented with powerful type systems. In Haskell, we can add the unit of a physical quantity to the type of every required function.

velocity :: Quantity Meter -> Quantity Seconds -> Quantity (Ratio Meter Seconds)
velocity s t = s / t

The compiler can then verify the function’s units. Calling velocity with a value of type Quantity Inches would not type check.

The more advanced features of the type system we make use of, the more mistakes we can prevent. In the beginning, it might take us a bit of back and forth with the compiler to make our programs type check, but once our program compiles, we are rewarded with the confidence that it will only require runtime debugging.

The expressive type system makes it easier to write correct programs.

The power of abstraction

In functional programming, functions are first class citizens. This means that functions are values, just like numbers or characters in other programming languages. This opens up the potential to write higher order functions: functions that operate on simpler functions or return new functions.

This allows us to abstract over common patterns that occur in writing functions. In an imperative language, you might write one function to sum up the numbers in a list, and another function to compute the maximum number in a list. In Haskell, we can write one general function reduceWith that encapsulates the concept of “use an operation to reduce a list to a single value”. The sum and maximum functions, then, are instances of this general function. For example, the sum function reduces the list by the addition operation. After writing our general reduce function once, we can define the instances as self explanatory one-liners.

sum = reduceWith (+)
maximum = reduceWith max

This level of abstraction allows us to follow the DRY principle (Do not Repeat Yourself) of software engineering. It also makes functional programs surprisingly short, and fewer lines of code means fewer potential errors.

The high level of abstraction makes functional programs expressive and concise.

As pure as possible

The high level of abstraction in functional programs is enabled because they compose well. It is easy to combine simple functions into more complex ones. What makes functional programs so composable is their purity.

Pure functional programs have no side effects and no mutable state. With pure code, we never need to worry about a function making modifications to some value that could break something else. We can ensure that the result of a function call will stay the same, no matter the context or how often we call it. This property is called Referential Transparency. It makes programs more intelligible since we only need to look at each function individually to understand its behavior.

Of course, there will be some parts of nontrivial programs that need to make use of side effects (i.e. input and output). However, the type system of Haskell allows us to make transparent where these impure steps are happening. For example, a function that performs input/output operations must make use of the IO type:

Press + to interact
printGreeting :: IO ()
printGreeting = print "hello!"
main = printGreeting

This way, we can encapsulate the impure operations of our code in a few functions that interact with the outside world. It is also clear to anyone reading the code where the impure steps happen and referential transparency does not hold.

The focus on pure functions makes programs easier to understand and compose.

Functional anywhere

Concepts from functional programming, such as powerful type systems or first class citizen functions, are also being added to more mainstream programming languages like Java and C++. And knowing how to design programs in a functional way can strengthen your programming skills in Haskell and other languages.

The determined Real Programmer can write FORTRAN programs in any language.

~ Ed Post, 1982