Revisiting Haskell after 10 years
Perspective on experimenting with Haskell again after almost 10 years
Happy new year, dear readers! 🥂
The beginning of the year kickstarts the urge of many people to write a retrospective post. I’m gonna do something a little bit different. I wanna talk about the Haskell programming language and what it looks like nowadays in comparison with 10 years ago according to my perspective.
I’m by no means a Haskell professional. It’s just a programming language that I sometimes play with for fun in my spare time and, spoiler alert, I enjoy it a lot. It may be biased since it’s my perspective, so bear that in mind.
Back to the past
My history with Haskell started almost by accident. I wanted to experiment with a programming language different than what I used to work with back then (Java, JS, and Ruby). There was this publisher in Brazil called Novatec where I used to buy programming books, and I bought this Haskell book:
This was my first contact with the language, and it indeed proved to be very different!
My first impression and experience
The first impact I had in 2014 when I started reading the book was the fact that the language had type inference. As someone who had C and Java classes in college, and did Java professionally at the time (and a bit of C# and ActionScript prior), I’ve given up on static typing until I tried Haskell. Why, you might ask? The Java tooling wasn’t so easy to work with at the time. Lots of crashes, IDEs misbehaving, and some projects had awful proprietary tools and frameworks involved, not to mention Java 5. I was delighted with Ruby and how fast the feedback loop was, and Bundler to manage dependencies simply didn’t stay in my way, it just worked. Anyway, Haskell restored my faith in static-typed languages, as readability and succinctness had not to be given up in exchange for correctness and speed.
If you’re curious to know what Haskell code looks like, here’s an example:
import Data.List
wordCounter phrase =
nub $ map (\x -> (x, length $ filter (== x) words')) words'
where words' = words phrase
And the output of the function call:
wordCounter "cow potato chicken potato nugget monkey potato cow potato"
-- [("cow",2),("potato",4),("chicken",1),("nugget",1),("monkey",1)]
So…
Soon I realized that Haskell was weirder than I thought. Yes, I’m talking about performing side effects. There was no concept of mutable variables, and to perform any side effect you had to use monads. I got stuck for quite some time using simple IO functions to read-write to the terminal, staying away from complex operations such as read-write to databases, spinning up web servers, and monad transformers to name a few—I will come back to transformers later.
This kind of challenge tends to repel learners given its steep learning curve but I was amazed by how I could track which function was doing certain side-effect operations by simply staring at the function definition, and how mutability was widespread and dangerous in C-like languages in comparison—though I could not make correct use of this power at my will yet.
In terms of tooling, there were:
GHC, the main Haskell compiler
Hugs, another known compiler in vogue at the time
Cabal, the building and packaging tool
Compiling programs using cabal was quite nice, as the tool provides all the common use cases you need during development. Adding third-party packages from hackage was most times straightforward, but using packages from other sources or packages conflicting with another package version you had specified on cabal was a nightmare. After 1 year, I guess, stackage appeared, but I had stopped with Haskell by this time.
I remember having a fun ride with Haskell, learning about performing side effects using monads, and writing immutable code by default. It changed how I was writing code in other programming languages, as it made me aware of how many uncontrolled side effects I was performing unconsciously, and how bad it could be for large applications. The code might look alien when you start but soon you get comfortable with it and start appreciating the conciseness. Just don’t take it too far and abuse symbols to write functions—maybe a topic for another post.
I had stopped with Haskell but the learnings remained. I still remember when I started slowly refactoring some parts of a Ruby application that I used to maintain, leading to an increased speed to run the test suite, by simply postponing some side effects and setting some boundaries on where it was allowed to do it or where it was forbidden.
Summarizing, the good parts:
The Haskell syntax seemed beautiful to my eyes.
The tooling wasn’t great but it wasn’t that bad.
The GHCI, the Haskell REPL, is super helpful.
I enjoyed a lot the fact that I could use Vim to write code in Haskell just like I used to do with Ruby and JavaScript.
Not everything was always a cakewalk:
I remember missing autocomplete but since the syntax wasn’t heavy I didn’t bother too much.
The compiler had some very cryptic error messages for a newbie.
Solving dependencies among conflicting packages was a real pain.
Monad transformers tricked me for a quite long time and I almost gave up.
Compilation time was too long if you’d rely on many third-party packages.
Fast-forwarding to 2024
Haskell looks practically the same. It has new improvements but it has not become a different language. I still have the same feeling as the fond memories of 10 years ago.
I recommend this article about what has been introduced in GHC 2021 (the most recent Haskell language standard):
The tooling evolved A LOT for the better!
The compiler now shows more helpful error messages and GHCup allows us to manage multiple versions of GHC, Stack, and HLS (Haskell Language Server) in a breeze. Compilation time is faster now, but I believe it is because hardware has become faster over the years. Unfortunately, cross-compiling is not yet as simple.
The advent of language server protocol made possible the creation of HLS (Haskell Language Server), and there are plugins for many editors, such as vscode-haskell, that allow you to have auto-complete, auto-import, and automatic function signatures—also available to your editor of choice. The whole feedback loop of editing, compiling, and running is greatly improved.
Writing Haskell programs that rely on third-party packages is still an issue when it’s a not actively maintained package. They get out of date with the base library (Haskell’s standard library), and you might see yourself in a situation where you need to downgrade to an older version. This is not exclusive to Haskell, but it happens more often than I’d like to assume. However, if you only rely on known well-maintained libraries/frameworks such as Aeson, Squeleto, Yesod, and Parsec, to name a few, it’s unlikely you will face troubles at all, you just need to be more mindful of what you add as a dependency. There’s stackage.org now, a repository that works with Stack, providing a set of packages that are proven to work well together and help us to have reproducible builds in a more manageable way—not the solution for all the cases but it’s good to have it as an option.
Monad transformer is one topic that made me lose countless hours going from book to practice and back in the past, but there’s more content available nowadays (books, websites, user groups), and you can also fire questions to an LLM to diminish your feedback loop as if someone more experienced was at your side showing you how to do it. I wish I had this at my disposal back in the day.
Haskell books worth mentioning
The Effective Haskell is the most recent book about Haskell, as of now. Not my favorite, but it is full of exercises to practice, and the last chapters—Building Efficient Programs and Programming with Types—are insightful.
The LYAH is by far my favorite book for beginners, however, it lacks exercises for you to practice, but you can still move along typing and playing with the examples shown, and it’s free to read online. It’s outdated but most of the code may still be valid with little to no changes.
The Real World Haskell book is also outdated, but can also be read online for free, and has many examples and exercises on writing practical and usable applications. Although I have not read the book to the fullest, I still recommend its monad transformers chapter, as it was the one that made it click for me.
Wrapping up
I’m excited about Haskell again! It sits in a very good spot giving its users the expressiveness and conciseness of modern languages with a good performance when you need to squeeze your hardware.
This post has become far too long and almost no Haskell code has been presented to curious readers. Soon I will write about my experience rewriting a command-line application in Haskell with substantial code to show. Stay tuned!
Thanks for writing this! I'm also coming back to do some work in Haskell after a while away doing other things, and I'm quite excited. I am *so* over writing code, and don't find it fun any more really... except with Haskell. I still find myself staying up until 2am trying to get some bizarre monadic thing to work, and when it does I feel absolute euphoria.