Learn Myself a Haskell

7 minute read

Once upon a time I actually studied mathematics. Overall it wasn’t a particularly pleasant experience. I guess although I was pretty good at “school mathematics”, “university mathematics” weren’t really for me. I quit and started doing what I do now, which is software development.

Anyways, during that time I had my first contact with functional programming. As a student are free to choose courses from a pool of “Wahlpflichtfächer” and a friend convinced me that we should pick this functional programming thing. His words were, I believe, “C’mon, it’s going to be fun!”. The course was called, duh, “Funktionale Programmierung” and was held by some Prof. Jens Knoop. We were supposed to learn how to do functional programming using Haskell.

Let’s just say that at the time, I was not quite ready for the subject matter. I feel like I simply did not have the stamina to actually sit down and try to understand some of the concepts provided in the lecture. I failed the course, and looking back, I feel pretty bad about that. It was basically laziness and arrogance (“What do you mean, there are no loops, what good is that?”) that caused me to abandon the whole affair. Fast forward to today.

FunkProg in 2016

In the curriculum for “Software Engineering and Project Management” functional programming is a mandatory subject, as a student you have to take it, whether you like it or not. Last semester was the time for me to face my past and try to learn Haskell - again. I went to visit the lecture of Professor Knoop - again. I admit, I was a bit scared, which turned out to be a good thing. I actually took the subject seriously, prepared ahead of time, and just generally approached everything more… professionally, I would say.

So for the rest of this write-up I want to describe what exactly I did to prepare. I want to highlight what kind of tools I used, and what might be helpful for other students. I think most of that stuff does not apply to Haskell only.

First of all, I made myself familiar with the language before the course actually started. During summer, I read Learn You a Haskell for Great Good. If you want to learn Haskell, I suggest you start with that book. Miran Lipovača does a great job explaining pretty much everything to know (for a beginner). Obviously, it still takes some work to wrap your head around some of the concepts, but I feel that it doesn’t get better than that.

Besides, you see what “proper” Haskell syntax looks like and learn some nice little… eh, syntactic sugar like guards beforehand. I can’t stress enough how much knowing the language beforehand helps with the exercises. That way you can concentrate on what you are supposed to do as opposed to how. (Funnily enough, this reminds me that I should do the same thing with C for next semester. Heh.)

When I start working in a language that I had no contact with before I really like to make myself familiar with the ecosystem surrounding that language. That usually means to get a grip on what editors might be used, which package managers are available and how documentation and tests are done. I like tests, and although writing tests really wasn’t necessary for the university course, it did give some nice benefits.

For the editor, I used SublimeText with Sublime Haskell. I try to use IntelliJ IDEs wherever I can, and although there are plug-ins to support Haskell in IDEA (I think), those did not appear to be very trustworthy. Using Sublime with whatever plug-ins I can find is usually my backup plan. There are some IDEs specifically for Haskell, but still… meh.

So, Sublime Haskell. I can tell you, I was getting a bit frustrated setting it up at first. It’s not super easy. Additionally, there are some quirks, so not everything works as expected all the time. Still worth it in hindsight. Sublime Haskell provides Syntax Highlighting, Auto-Complete, and most importantly, integrated linting. Which, for a beginner, is worth gold. Not only does linting show you where you actually made syntax errors, it also tells you which syntax constructs might be optimized.

Additionally, I found that using SublimeREPL was rather helpful in this context. It enables you to open GHCI in Sublime, so you can both write and test your implementation within Sublime. Not necessary (as you could just open a terminal window for that), but still kinda nice. Again, syntax highlighting and stuff, you know?

Speaking of testing, I know I have an addiction. I simply can’t write code without at least writing some tests for it. In the case of Haskell I used Hunit for tests. There are other frameworks, but this one sufficed for me.

You might ask “Well, this are just some small university exercises, why would you write automated tests for that?”. You have a point. As I said, addiction. Automated unit tests for the exercise solutions certainly are overkill, but they still give some benefits. First of all, you easily see how functions should be used. Secondly, and I think that’s the real benefit here: The exercises were evaluated two times. If you made some mistakes the first time, you would have a week to correct the mistakes and submit a second time. However, if the second solution was worse than the first one, you would receive fewer points than before.

Here unit tests provide a real benefit. Even when I made changes a week later, I could always be sure that the solution was still 100% correct (without manually testing everything again). Here’s what a bunch of code together with its tests looks like:

-- | Creates a canonical multiset from a given tree.  
--
-- >>> mkCanonicalMultiset (Node 'd' (-3) (Node 'f' 0 Nil Nil) (Node 'a' 1 Nil Nil))
-- (Node 'd' 1 (Node 'a' 1 Nil Nil) (Node 'f' 1 Nil Nil))
mkCanonicalMultiset :: (Ord a,Show a) => Tree a -> Multiset a
mkCanonicalMultiset x = mkMultiset' (max 1) x Nil

mkMultiset' :: Ord a => (Int -> Int) -> Tree a -> Multiset a -> Multiset a
mkMultiset' _ Nil set = set
mkMultiset' op tree@(Node _ _ left right) set =
    mkMultiset' op left $ mkMultiset' op right $ insert' op tree set


multiSetTests :: Test
multiSetTests = test [
    "isMultiSet"
    ~: "should return true"
    ~: True
    ~=? isMultiset (Node 'b' 1 (Node 'a' 3  Nil Nil) (Node 'c'  3 Nil Nil)),
    "isMultiset"
    ~: "should return false"
    ~: False
    ~=? isMultiset (Node 'b' 1 (Node 'a' (-2) Nil Nil) (Node 'c'  3 Nil Nil)),
    "isMultiset with invalid searchtree"
    ~: "should return false"
    ~: False
    "mkMultiSet from search tree"
    ~: "creates multiSet"
    ~: (Node 'd' 1 (Node 'a' 1 Nil Nil) (Node 'f' 0 Nil Nil))
    ~=? mkMultiset (Node 'd' 1 (Node 'f' 0 Nil Nil) (Node 'a' 1 Nil Nil)),
    ]

Even if you need to manually test, those automated tests provide a nice starting point, don’t they?

Now, as you can see I opted to also document my solutions. The first four lines provide a simple description as well as some info as to how to use the function. The whole thing can be rendered as HTML or whatever using Haddock. Again, this might be overkill, but it served to familiarize myself with the whole ecosystem. After all, I might some day write more Haskell, and then this knowledge might come in handy.

One last thing that I used was a tiny script to convert Literate Haskell Script to Haskell Script. You see, some exercise had to be written in LHS, and since nobody usually does that, syntax highlighting and such don’t really work for it. The solution is to write everything in normal Haskell Script and convert to LHS later. Worked pretty well, apart from some documentation not being properly translated. That wasn’t too hard to do manually though. You can find the script here.

Conclusion

I had a lot fun learning Haskell. If you learn (or have to learn) Haskell in a similar setting as I, I recommend you do the following. Read some literature before hand (for example, Learn You a Haskell for Great Good). Make yourself familiar with the ecosystem, especially available IDEs. Having auto complete, syntax highlighting and linting is a must. If you are super freaky you might also consider writing some tests for your solutions. Most importantly: Have fun!

Updated: