A Brief Overview of Noteworthy Haskell Features

After getting interested in Haskell thanks to this video (both educational and quite funny, highly recommended), I decided to start learning language. It’s been (and continues to be) a strange experience, much different than learning Python or Objective-C. In the beginning it felt a bit like relearning programming.

To teach myself I decided to write a spaced-repetition learning program, inspired by Anki. I’ll cover some aspects of Haskell I struggled to understand, so that other beginners may avoid the same mistakes I did, and give a brief overview of some of Haskell’s features that I found particularly useful. All the code examples will be taken from the spaced-repetition program I wrote, the source of which can be found here, if you’re curious about how a piece of code fits in with the rest of the program.

Some level of programming knowledge is assumed, mainly a basic understanding of functions and types.

Disclaimer : I’m still very much a beginner Haskell programmer, so be aware this isn’t a guide from an expert, just a brief overview of some noteworthy features that an amateur found interesting enough to write about.

Pattern Matching and Guards

Pattern matching and guards are easy to understand, while also being incredibly useful. Honestly, I wish every language I use had pattern matching. Here’s a quick example of how it works :

The first line here is the type signature, and it means that getAddAction is a function that takes a list of things of type Deck, and returns a Maybe AddAction that is in the IO monad. That’s probably confusing, and I’ll cover the type system a bit later. The second line uses pattern matching. The [] tells Haskell “if the argument passed is an empty list, run the following code”. If the pattern doesn’t match, it will go to the next pattern, which is the third line here. Patterns like x, y, _, or someLongName12, are catch-all patterns. They’ll match anything. The underscore means to disregard the value, since we only care that it’s not empty (which we know because the first pattern wasn’t matched). Essentially, the third line is saying “I don’t care what the argument is, and I don’t need it, just run the following code”.

Pattern matching helps to get rid of long, awkward functions that contain multiple if-statements to check what form the arguments take. It also makes it easier to see at a glance what a function will do with different inputs. One of the greatest advantages of this is that the compiler can tell you if you’re missing a pattern, so it’s impossible to write a function that doesn’t match some input, as long as you pay attention to the warnings. This means not having to read through a function with 5 if-statements to check whether they cover all possible cases.

 Function Composition

Function composition is a bit trickier, and can be abused to make some pretty unreadable code, but used in moderation, it can make for more concise and readable code. Here’s an example of function composition :

The first line is another type signature, somewhat easier to understand than the last example. The function takes a list of things of type Deck, then returns a Bool in the IO monad.

Skipping to the third line, the specifics of how aren’t too important, but the result is that shouldQuizList is a list in the IO monad that contains boolean values (ex. IO [True, False, False, False]).

The second line is where the function composition happens. The >>= basically means “Take the value stored in the IO monad, and apply it to the function on the right”, it’s used here as a handy way to avoid a do block and having to name the result of taking shouldQuizList out of the IO monad (one of the two hard things in computer science). I’m interested in whether there are any elements that are true, so I initially had something like this :

But if you look at the type signature for the function, you see that the function needs to return an IO Bool, but it’s just returning a Bool now. So now we use function composition to get it in the IO monad, and turn True elem into return . (True elem). The dot allows us to compose two functions, so the result of the second line is a function that tells Haskell “Take this list of bools in the IO monad, pass the bools to the function on the other side of the >>=. Then check if True is an element of the list, and return the result of that”.

Currying / Partial Application

When I first heard of partial application, I thought ‘That might be useful in theory, but I’m sure I won’t use it very often, I’ve never seen a need for it before’. Surprisingly, when I started looking out for places where I could use it, I found it actually helped improve legibility in many cases. I’m going to use the same example as I did with function composition, as it might have been confusing seeing the True elem function, element of what? This is partial application at work. Here’s the code again so you don’t have to scroll up :

elem is a function that takes two arguments, a thing, and a list of things of the same type, and checks if the first thing is an element in the list, so elem 5 [1,4,6,5] is True. elem can be used as an infix operator by using backticks, so 5 elem [1,4,6,5] is also True. We have a list of bools and we want a function that takes the list and returns True if True is an element of the list, or False if True is not an element of the list. This is the type signature of elem :

elem :: Eq a => a -> [a] -> Bool

So it takes a and a list of as, then returns a Bool. But we want a function that takes just a list of as and returns a Bool. Thankfully, we don’t have to write our own function for this. We can just use partial application to make elem behave how we want it to. True elem is the same thing as elem True, just more readable. elem True actually gives us another function, but instead of a -> [a] -> Bool, the type signature of the function it gives us is [a] -> Bool, which is exactly what we needed. Partial application is incredibly useful, but it’s sometimes hard to see uses for it when learning Haskell, so keep an eye out for opportunities to use it, you might be surprised at its utility.

The IO Monad

Well, I’ve saved the best for last, if best can be defined as “most confusing and unintuitive”. That’s not a criticism of how Haskell handles IO, I’ve come to realize that it actually helps quite a bit to have such restrictions on IO. Still, it was a source of a lot of frustration when I was learning Haskell, and I assume others have similar difficulties coming from languages where keeping functions ‘pure’ isn’t a concern. I still don’t fully understand monads, I’m sure that will take quite a bit more time with Haskell, but I know when to use them, why they are necessary, and how to deal with user input while keeping myself sane. There are a couple things to keep in mind.

The first (and this was the hardest for me to come to terms with) is that if you have a function that returns something that’s in the IO monad, you can’t have a function that calls it return anything that isn’t in the IO monad. An example would probably help, let’s say you have these two functions :

It doesn’t matter what the rest of the code is in functionX, it’s impossible for it to return anything that isn’t in the IO monad, because it called a function that is in the IO monad. If another function calls functionX, it too has to be in the IO monad, there’s simply no way to avoid it.

The second thing is you should almost never use unsafePerformIO. It might seem tempting when starting out, since you might have something like IO Bool, and you want a Bool. There’s almost always a better way to do it, and it usually involves arrow notation, either something like this :

Or something like this :

In this case the latter is clearly more legible and shorter, and which way you choose to do it will certainly vary, but the ‘correct’ way will almost never involve something like this :

Strong Typing

I realize this isn’t a feature unique to Haskell, but I thought it deserved a mention. Coming from Python, I thought that duck typing was great, and that having to specify types would be tedious. When my program got to be more than a few dozen lines, the benefits of a strongly typed language became more clear. I had heard that typing gets rid of a huge amount of bugs, and lets the programmer catch errors at compile-time instead of run-time, but never gave much credence to these claims. I underestimated how many of the bugs in my Python programs were really type errors. I probably wrote 400-500 lines while writing Clanki (~300 lines now), and the entire time I had 3 run-time errors, 2 of which Haskell warned me about before compiling. That was mind-blowing coming from Python. It was nice to know that if it compiles, it will almost always run properly.

Conclusion

These are just the features of Haskell which I found the most interesting/useful. There are many more, and I may add to this list as I encounter them. As I said in the disclaimer, I’m still a beginner so if anyone has corrections/improvement, feedback is more than welcome.

Leave a Reply

Your email address will not be published. Required fields are marked *