Top Ten Reasons I Like Haskell

A few weeks ago I wrote about some of the differences between functional languages (like Haskell) and imperative languages (like C or Java). While I made some good points in this post, I don't think I gave enough credit to Haskell. After learning more about Haskell, it has become my favorite programming language. In this blog post, I will talk about some of reasons I like Haskell. Please keep in mind that I am relatively new to Haskell and the contents of this blog post are about my experience as a beginner. That being said, the intended audience of this blog post is people coming from an OOP background that are looking for something better.

Purity

In a Purely Functional language like Haskell, it's impossible to mutate objects in memory. While this may seem like a limitation, I assure you it is not. In Haskell, functions must take input and return output, but they may not have side effects. This has great benefits as it makes it easier to reason about code, and the compiler can make more optimizations since it knows that the state of any given object isn't going to change over time. This makes Laziness possible, another reason why I really like Haskell. This object purity has positive implications towards parallel programs, as race conditions are no longer possible. Race conditions are typically a major problem when writing parallel programs, but they are not even possible in a purely functional language like haskell. Since parallelism is growing in popularity, functional programming languages are becoming more widely used for precisely this reason (and many others that make it suitable for parallelism).

Laziness

Laziness is a concept unique to Haskell (other languages may offer it, but in Haskell it is the default behavior). Laziness is the idea of delaying a computation until it's result is really needed. Under eager evaluation, a computation is run whenever the program calls it. For lazy evaluation, This means that your program will never do more work than is necessary. Laziness allows for infinite data structures, which makes code more extensible, abstract, and simpler. Laziness is sometimes difficult to reason about, as the program doesn't behave exactly as the programmer specifies. This can sometimes be an issue and cause space leaks, and sometimes eager evaluation is more appropriate. I feel like the benefits of laziness outweigh the drawbacks.

Ability to Work with Functions

In functional languages, you can do a lot more with functions than you could in imperative languages. You can pass functions to other functions, you can partially apply functions to get new functions, you can easily use function composition to take two functions and get one. These are all things that are not typically possible in imperative languages.  In java, there are workarounds that provide some of this functionality, and in python you can also do some of these things, but Haskell really shines because of it's elegant, intuitive, and simple syntax for capturing these high level abstractions.

Syntax

If you are learning Haskell coming from an imperative language like Java or C++, Haskell syntax will look foreign and strange to you. However, Haskell syntax is much better than that of imperative languages. The creators of Haskell took great care to provide syntactic sugar for all of the important functions. This results in the ability to express complex but commonly used paradigms in a simple and readable way. As an example for non-haskell programmers, you can define an list of odd numbers up to 100 like this: [1,3..100]. Pattern matching is one of my favorite things about the Haskell syntax. Pattern matching allows you to abstract away control structures like if and else if. The result is a syntax that very closely resembles the corresponding mathematical definition. For instance, if I wanted to write a function that multiplies a list of numbers by 2, I could use the following definition:
double :: [Int] -> [Int]
double [] = []
double (x:xs) = (2*x) : double xs
Where the input is first pattern matched on the empty list, if so it returns the empty list. Otherwise, it pattern matches on (x:xs) where x is a single Int and xs is a list of Ints ([Int]). Pattern matching typically abstracts away the need to call getters or selectors and it allows for simpler code.

Higher Order Functions

Higher Order Functions are a set of abstractions that take common design patterns and extract away common components. The double function from the previous section is a great example of a design pattern that was extracted. In fact, the same thing can be expressed as double xs = map (2*) xs. Pretty cool, right? Well map is just one of many abstract functions, and there are many more like it that make writing code fun and exciting! Some other important higher order functions are filter and fold. Filter is pretty self explanatory; the signature for filter is filter :: (a > Bool) -> [a] -> [a]. It takes in a function (a -> Bool) and a list and returns a new list with all the elements from the original list that satisfy the given function. For example, let's say I had a function even :: Int -> Bool, then I could get a list of all even numbers from a given list by using filter: filter even list. In an imperative language, it would require a for loop with at least 3-4 lines of code to achieve the same thing.


Rich Library of Functions

In addition to it's higher order functions, Haskell provides many commonly used functions to make the development process less repetitive. You can flatten a list of lists into a single list with concat, you can remove all duplicate elements in a list with nub, you can partition a list at a specific index using splitAt or based on a certain condition using break or span. The list goes on and on, and the result is there is rarely an instance where you need to implement your own function.

Hoogle + Hayoo

Hoogle and Hayoo contain Haskell documentation, and they provide a search engine interface so you can easily search for the functions you are looking for. Whenever I want to know how to do something in Java, I search on google how to overload sort function in java. This can sometimes be a tedious process and I don't like sifting through stackoverflow answers or java docs. Hayoo is very nice, as long as you know the name or signature of the function you want, it's easy enough to find. The signature for the function I am looking for would be (a -> a -> Ordering) -> [a] -> [a]. Where the first input returns an Ordering (LT, EQ, or GT), the second argument is the input list, and the output is the sorted list. Querying Hayoo with this signature immediately yields the function I was looking for: sortBy :: (a -> a -> Ordering) -> [a] -> [a].


Type Inference

Haskell can infer a lot of things about your code. It knows the type of every variable in your program from relevant bindings. The user doesn't need to specifically specify what type every variable. Haskell can infer the signature from type signatures, function calls, etc. Haskell's type inference was one of the thing that really surprised me when I realized just how powerful it was. Consider the following example, where I have a function f :: Int -> Int and I want to get f 3, but I only have "3" (maybe it's coming in from stdin). Well it seems that I'd need a function String -> Int, but as it turns out Haskell has a much more powerful function, called read: read :: (Read a) => String -> a. How is that possible? The function can return anything? This is possible through type inference! If I have the code read "3", Haskell has no idea if I want read "3" to be and Int, Double, Char, etc. But if I have f (read "3"), Haskell can infer from the signature of f that I want (read "3") to be an Int and it makes the relevant conversion. This is incredibly useful and awesome!

Automatic Derivation of Polymorphic Behavior

Automatic derivation on behavior is a perfect example of where Haskell excels. For example, if I defined my own class data Fraction = Fraction Integer Integer, and I wanted to be able to check if two Fractions are equal, I can simply derive the Eq typeclass! There's no need to provide my own implementation (although I can if I want to). data Fraction = Fraction Integer Integer deriving (Eq, Show, Read). When you derive a type class in haskell (like Eq, Show, or Read), Haskell provides a default implementation that usually does exactly what you need. This is an excellent feature that makes code simpler and more robust.

Hackage and Cabal

Hackage is a vast repository of Haskell modules and plugins developed by the dedicated Haskell community but not included in the standard haskell distribution. Using cabal, you can easily install and integrate these modules into your code so you never need to rewrite code. And since the Haskell community is full of talented mathematicians and programmers, you can be confident that these packages are reliable and efficient. Cabal is really easy to use and is automatically included in the Haskell distribution. It allows you to install any module from Hackage so you can seemlessly integrate it into your code. It's extremely convenient and easy to use which is why I like it so much.


Concluding Thoughts

Haskell never ceases to amaze me with all it's features and abstractions. It is truly the most enjoyable programming language I have ever used. While the above list is not exhaustive and there are many other reasons why Haskell is great, this should at least peak your interest to check it out if you haven't heard of it before. The best place to start for beginners is Learn You A Haskell.

Comments

Post a Comment

Popular posts from this blog

Multi-Core Programming with Java

Efficiently Remove Duplicate Rows from a 2D Numpy Array

Beat the Streak: Day Three