May 25, 2020

1775 words 9 mins read

Getting started with Haskell

In a episode “A touch of Haskell”, we learned the basic concepts of functional programming and how these are used in Haskell, however it was a rather dull post in the sense that it didn’t really explain how to get started at kicking butts with Haskell, and this is exactly what this post is really about. We’ll begin by explaining how to get the Haskell compiler and latter on review the ecosystem and how the philosophy differs from those of communities in more mainstream languages. This is something I would like had seen when I got started with Haskell.

Installing the compiler

Haskell (the language), is a standard that every decade or so has gone through changes in terms of syntax or behaviour expected of a Haskell program , here you can read more about it, currently most of the community uses Haskell2010, in the past there existed various compilers that differ in implementation but all of them adhered to the standard, nowadays GHC is the defacto Haskell compiler used, other implementations of the standard became stale e.g. Hugs.

So in order to compile Haskell code we need to download the Glasgow Haskell Compiler, GHC for short.

Interesting fact: before Haskell I didn’t know anything about Glasgow, it turns out to be a city in UK where in the University of Glasgow the GHC compiler was created. Some refer to GHC as the Glorious Haskell Compiler because it is.

In order to get going with GHC and Haskell we can have it in multiple ways:

  • By downloading the Haskell Platform
  • By using the Stack build tool
  • By using ghcup utility.
  • By using Nix.

I’m going to write about all four next just for clarifying what you’ll see spoken about in the community or just for the one who cares, however if you just want to get started in no time I recommend using Stack.

The Haskell Platform

The Haskell Platform is a bundle to get us going with Haskell in any OS the simplest way possible, although is not recommended if you plan to use Haskell for real projects (we’ll see why in a second). The Haskell platform can be downloaded from here and bundles the following programs:

  • The GHC compiler.
  • The Cabal build system.
  • The Stack build tool.
  • Some included packages.

As mentioned above it often not recommended unless you know your way through the tooling, this is because most beginners have trouble with it at some point due attempting to compile code whose dependencies conflict with some of the already included packages or because the code they try to compile was built for a different version of the compiler, this is mostly because by using the Haskell platform you’ll end up with a specific version of the compiler plus some packages installed globally in the system then other projects which may require other versions of such dependencies or other version of the compiler will not always work. Both Cabal and Stack are tools that help solve this problem.

Usually when you install a package named Haskell using the system packages index (such as apt on Ubuntu) you’ll end up installing the Haskell platform.

Cabal

“Hey! This isn’t one of the four options you listed above!!”, I know man, but in order to talk about what are the other alternatives I must explain the most popular build tool for Haskell projects, that is Cabal. Let’s move on…

Cabal is a build tool system and is compromised of:

  • The cabal-install program that you use via CLI with the command cabal.
  • A spec format for files with the .cabal file extension.
  • A library that parses and understands .cabal files.

This is the “official” build tool for Haskell projects, think of it like the npm of Haskell (although not quite). Haskell projects contain a .cabal file that has information about the project (you can see it as the package.json in Node projects), whether it is a library or if it contains executable binaries, which dependencies and versions it requires, what are the test suites defined, where are the tests files and so on, why they didn’t stick to JSON instead of creating their own spec format? I do not know.

By using the cabal-install program we can build Haskell projects using the cabal command. Cabal is under constant development and their philosophy of how it should work has changed over the last years, before that, Cabal was known for introducing a problem called Cabal Hell where it managed system-level dependencies making it prone to have dependency conflicts across several projects, this is like PHP’s PEAR but nowadays you can use Cabal to manage project-level dependencies using a sandbox, more like PHP’s Composer.

However, as an answer to the Cabal Hell problem, Stack was born, an alternative build tool leveraging the Good parts of Cabal.

Stack

Stack is a build tool that popularized reproducible builds in the Haskell community, coming from other communities such as Python, you may identify this tool as a combination of both pip + pyenv, or Node’s NPM + NVM, Ruby’s Bundler + rbenv. Stack under the hood uses Cabal, that is, you still have a .cabal file in your project and you still use the Cabal library that reads these files, but you no longer use the cabal command (provided by cabal-install program) to interact with it, instead you use the stack command.

Stack has the ability to download a GHC compiler for you and enables you access to a repository of packages that are guaranteed to work with your compiler version, the installation of the compiler is done in a isolated location and doesn’t affect any other installation of GHC you might have, it doesn’t even conflict with Cabal. I recommend reading the official site to learn how to install it and what it can do, but here’s the one liner for the lazy:

$ curl -sSL https://get.haskellstack.org/ | sh

Once you have Stack installed you can use stack setup to download a recent version of the GHC compiler, and after you’ve written a simple Haskell file with .hs extension you can compile and run it with:

$ stack runghc MyFile.hs

If the sub-command runghc is tripling your fingers you can use runhaskell instead, that is just an alias and does the same thing.

With Stack you can also create a basic scaffold of a project using stack new my-project-name and it will setup a GHC compiler specific to that project, once you enter your project folder you can add new dependencies and when building the project these will be downloaded and confirmed to work since these are from a curated package set known as Stackage that tracks which version of which packages build correctly with a given version of the GHC compiler. Many users that don’t like Stack (due to being loyal to Cabal or other ways to manage Haskell projects) recognize the value that Stackage brings to the Haskell community and in fact you can use Stackage with Nix instead of Stack.

I recommend to anyone wanting to give Haskell a try begin by installing GHC through Stack, is the easiest and friendlier method.

ghcup

There’s a command line tool called ghcup it was directly influenced by the rustup Rust’s utility, we can compare it to NVM, rbenv, pyenv, etc. It let us manage GHC installations in our system, you can read more about it in its official website but in short we can install it like this:

$ curl https://get-ghcup.haskell.org -sSf | sh

Once installed you can setup a GHC compiler in your system like this:

$ ghcup install ghc

But you can grab a specific version as well:

$ ghcup install ghc 8.0.2

And you can also use ghcup to install the Cabal build tool:

$ ghcup install cabal

And also gives us a way to easily install HLS the Haskell Language Server so we can integrate with our editors and have Haskell code intellisense:

$ ghcup install hls

After stack this is the most friendlier way to set up Haskell, and in fact these can be use together!

Nix

Nix goes a step beyond any build tool, beyond any package manager, is like a merge between containers and tools like NPM, Composer, etc. Nix has package sets with libraries and dependencies for many programming languages, not just Haskell, and not only that, it also manages system level dependencies down to the standard library, if anyone is mad about true reproducible builds Nix is the answer, and it so happens that the Haskell community wants purity in every aspect of their lives, it is no surprise that the Haskell community has adopted Nix to the point that most of Nix users are also Haskell developers.

By using Nix you can install the GHC compiler, and Cabal as a build tool, you can create a Nix environment to manage project level dependencies and system level dependencies, you can also build a Haskell project using Nix but Cabal is recommended for this since it supports progressive builds (maybe this has changed lately but I’m not sure).

The problem is that Nix is complex but many say is very well worth the effort, the key to tackle Nix is to learn the Nix language (that is also functional), then you can even use it to manage your system dependencies in your development workstation, there exists NixOS, a Linux distro that configures your whole system as if it was a project, where the calculator, Firefox, your media player and even your linux kernel are just “dependencies” which must be present at specified versions in a manifest file, you can even declare which VIM plugins you should have installed in the system and what color scheme should be used, you can do rollbacks seamlessly and by using your configuration files switch to a new computer and have the same environment deployed from scratch without issues, package by package.

Conclusion

Now, let’s sumarize:

Cabal Stack Ghcup Nix
Can install GHC? No Yes Yes Yes
Global installed GHC? - No (by default) Yes (switch versions easily) Yes1
Can manage dependencies? Yes Yes No Yes
Can build a project? Yes Yes No Yes2
Supports sandbox mode? Yes (not by default) Yes (by default) - Yes (not by default)

Some will say ghcup doesn’t belong in that table since it is not a build tool as the others, but hey! I did talk about it right? have to include it then 💅.


  1. By using Nix, it is possible to have multiple versions of the GHC compiler installed at once without any conflict. ↩︎

  2. This is nice feature to have in CI but not so much while in development. ↩︎