This blog post describes the two main ways of managing and building Haskell projects: using the
stack build tools. The blog post doesn’t try to replace documentation for any of the building tools, nor does it try to cover all possible ways to build a Haskell project. Rather, it tries to give beginners step by step instructions on how to create and build a simple project. The goal is to reduce the confusion, especially for those who have just started working with Haskell. While documentation is good to gain a deep understanding, sometimes you just need things to be done straight away.
Haskell is a compiled programming language that also has an interpreter. The interpreter allows you to experiment with packages, like those from Hackage (Haskell central packages repository), in a small single file or even in the interactive REPL called
ghci. But for big projects, it’s better to organize the source code into modules as it will help with maintainability. Build tools like
stack can help you manage the building process.
Hackage has a lot of libraries and finding the function you need can be frustrating. Luckily, you can use hoogle to search by name or type. Even then there are several build tools for Haskell and deciding which one to use can be difficult. This blog post focuses on two such tools:
- Documentation: https://www.haskell.org/cabal/users-guide/
- Documentation: https://docs.haskellstack.org/en/stable/README/
These tools are chosen because they are the most popular and mature. I won’t make any claim on which one is better, instead I will give you an overall idea of what the package development looks like for each of them. Hopefully, by the end of this post you will be able to choose which approach fits you more.
cabal in Haskell is semantically overloaded. In Haskell,
cabal can refer to:
- The build tool for Haskell projects.
- Format of files with extension
- The configuration (modules, dependencies, metadata) of a package
foomust be written in a file called
foo.cabalwith a special syntax that can be read by
- The configuration (modules, dependencies, metadata) of a package
- Haskell library
- This is a library used by
cabal-installthat implements a parser for the
- This is a library used by
PLEASE DON’T USE THE WORD “CABAL” WITHOUT CONTEXT!
In this blog post when I use the word
cabal I mean
cabal-install. Otherwise I will specify it explicitly.
ghc can both be installed through
Use the following commands for installation:
$ sudo add-apt-repository ppa:hvr/ghc $ sudo apt update $ sudo apt install ghc-8.6.3 cabal-install-head
NOTE: It’s best to install
cabalHEAD since it has better support for the commands we will use to build projects with
You can check the versions of the installed binaries like so:
$ /opt/ghc/bin/ghc --version The Glorious Glasgow Haskell Compilation System, version 8.6.3 $ /opt/ghc/bin/cabal --version cabal-install version 184.108.40.206 compiled using version 220.127.116.11 of the Cabal library
To make development easier, you can also add those binaries to your
$PATH environment variable.
$ echo 'export PATH="$PATH:/opt/ghc/bin"' >> ~/.bashrc $ . ~/.bashrc
Cabal: Mac OS🔗
cabal for Mac Os you can just use
$ brew install ghc cabal-install
If you want to work with multiple GHC versions on macOS, you may find neat
haskell-on-macos.py script useful:
The easiest way to install
ghc for Windows is to install the Haskell Platform, which you can do following the instructions at:
As an alternative you can
chocolatey, a package manager for Windows:
To create a new project you can run the
cabal init command. Cabal will then guide you through the interactive process, and ask you several questions regarding the structure of your project.
$ mkdir cabal-example # create project directory $ cd cabal-example # go into this directory $ cabal init # initialize project in this directory
See demo of
cabal init command below:
As an alternative, you can use the
summoner can generate for you much more than what
cabal init can, and it won’t ask you any redundant question. However, you probably should just use
cabal init for your first Haskell project because
summoner requires installation to be done first. Below you can see demo of
Cabal project structure🔗
If you’ve chosen to create
src as a directory
After initializing your package with name
cabal-example which builds
Library and has
src/ as code source directory, you will get the following structure:
$ tree cabal-example cabal-example/ ├── ChangeLog.md ├── LICENSE ├── Setup.hs ├── src/ └── cabal-example.cabal 1 directory, 4 files
cabal-example.cabal file describes the structure of your package. Here is how it looks like for our example project:
-- Initial cabal-example.cabal generated by cabal init. For further -- documentation, see http://haskell.org/cabal/users-guide/ name: cabal-example version: 0.1.0.0 -- synopsis: -- description: license: BSD3 license-file: LICENSE author: Dmitry Kovanikov maintainer: email@example.com -- copyright: category: Data build-type: Simple extra-source-files: CHANGELOG.md cabal-version: >=1.10 library -- exposed-modules: -- other-modules: -- other-extensions: build-depends: base >=4.11 && <4.12 hs-source-dirs: src default-language: Haskell2010
Put your source code under the
src/ folder. Don’t worry about the
Setup.hs file as it is not needed most of the time. You can ignore it or even delete it.
Building the project with cabal🔗
To build your project, you need to have some code. Let’s create a
Dummy.hs file inside the
src/ directory with the following content:
After that, let’s replace this line inside
cabal that this module is now part of your package.
Then, you will need to update the Hackage index, so that
cabal is aware of the most recent versions of the Haskell packages. To do so run this command from your package root directory:
$ cabal new-update
You should run this command only in one of the following situations:
- You’ve never run this command before.
- You’ve deleted the
- You want to use a newer version of a library that has become available after you last executed
NOTE: If something goes weirdly wrong when building dependencies (like scary linker errors) then deleting the
~/.cabaldirectory might help.
Finally, to actually build the package, run the command:
$ cabal new-build
cabalalso has a
updatecommand without the
new-prefix. Don’t use them! Use the commands with the
new-prefix instead: they just work better. Implementing proper build tool is a really difficult task and it’s not immediately obvious how things should be done. But this doesn’t mean that once you figure out a better way of doing things you should implement it under the old interface. Package authors care about their users: they keep the old way to build packages so people can continue using it, even though the new proper way is available (through the
new-prefixed commands). Old commands will be replaced by new ones in
cabal-3.0and this prefix will be removed.
Adding dependency in the .cabal file🔗
All dependencies should be specified under
build-depends field in the
.cabal file. By default, every new package has the
base library as a dependency. But there are many more packages on Hackage! For example, to operate with random values we can use the
random package. In order to do that you will need to modify the
build-depends field in the
.cabal file in the following way:
build-depends: base >=4.11 && <4.12, random
And then, we will be able to use any function from this package!
cabal new-build to ensure that everything builds.
NOTE: Usually there are multiple versions of a package with the same name. You may notice that version boundaries are specified for the
basepackage in your
.cabalfile. If you have a lot of packages without explicit version boundaries,
cabalmight not be able to build your project, because it won’t know which versions of the packages to pick.
Okay, we wrote some functions. So how about testing them? As mentioned earlier,
ghc has an interactive interpreter. You can run it with the following command:
$ cabal new-repl
And then you can evaluate the code we wrote in REPL:
You probably won’t see the same exact numbers as output for the
dice function. But there’s a
1/36 chance you will.
Cabal: adding executable🔗
Evaluating functions in REPL is really fun! But usually programs are implemented to be used as executables later. As I mentioned earlier Haskell is a compiled language so you can have native binary. To do this you need to add
executable to the
NOTE: Top-level sections like
executableand others are called stanzas in the cabal format specification.
Let’s introduce an executable stanza by adding the following lines at the end of our
NOTE: We add
build-dependsto be able to use functions from our
librarystanza. No need to specify version bounds for
basehere since they are derived from the
Now, we need to create the
Main.hs file in the project root. You can put anything you want as long as this file contains a function
main :: IO (). Use your imagination! Mine is enough only for writing something like this:
We gave our executable name
my-exe so we can launch it by running the
cabal new-exec command:
$ cabal new-build # don't forget to build your project after changes! $ cabal new-exec my-exe
Congratulations, you just run your first Haskell program!
For a more detailed but still beginner-friendly introduction to
cabal, I recommend this podcast:
Even if you are only interested in using
stack you should read the
cabal section first, as I will refer to it in some parts.
You can find instructions for how to build
stack at the beginning of the
You don’t need to install
ghc separately since
stack will download a suitable compiler version for you.
stack on any unix system is really easy:
$ curl -sSL https://get.haskellstack.org/ | sh
For Windows, you can download the official binary directly from the website:
To create a new project named
stack-example you run:
$ stack new stack-example
NOTE: To successfully finish the project initialization with
stackyour working machine should have an internet connection since offline mode has not been implemented yet.
cabal init command
stack new doesn’t ask you any questions. It just creates the project according to a default template. However, you can specify an existing template (or even create your own one):
Alternatively you can use the
summoner tool to create your projects. Any
stack template describes a static project hierarchy with only simple configurable metadata like user name. But sometimes you might want more, like the ability to add
benchmarks stanza to your package. Generally, you don’t want to have multiple templates that are just slightly different versions of one major template.
summoner can also automatically create a GitHub repository for your new project, so it is somewhere in between the
cabal init and
stack templates, but with some extra features.
Stack project structure🔗
The directory hierarchy for the default
stack template looks like this:
$ tree stack-example/ stack-example/ ├── app/ │ └── Main.hs ├── ChangeLog.md ├── LICENSE ├── package.yaml ├── README.md ├── Setup.hs ├── src/ │ └── Lib.hs ├── stack-example.cabal ├── stack.yaml └── test/ └── Spec.hs 3 directories, 10 files
Here you see that many more files were created for you than with
cabal. Here are
test-suite stanzas added to the
.cabal file with the corresponding Haskell code being put into the
test/ directories respectively.
stack.yaml file contains some extra configuration for
package.yaml file contains the packages description in an alternative format that is used by
hpack. TL;DR Instead of writing package description in
cabal format you can write it in YAML and you get some extra features like automatic modules discovery.
hpack then generates a
.cabal file using information from
package.yaml. But if you don’t want to deal with
hpack for now you can just delete
Building stack project🔗
Use the following command to build the project including
test-suite stanzas, and run tests as well.
$ stack build --test
stack will automatically download the proper GHC version during your first build.
One of the main differences between
cabal is how they determine the proper version for package dependencies. They both use the
Cabal library to parse
.cabal files. However,
stack uses the notion of LTS resolver. In simple words: resolver is just a Hackage snapshot where packages can work with each other smoothly. You can check
stack.yaml file to see which resolver you use. Look for a line like this one:
NOTE: This is the only line in
stack.yamlthat is required for stack to work in most cases. The default
stack.yamlfile will contain a lot of redundant stuff. You can safely delete everything except this one line, unless you need some more advanced usage.
Adding dependency to stack project🔗
Adding dependency for
stack is almost the same as for
cabal: you just add the library you want to the
If you are wondering which version of the package from
build-depends will be used in your project, you can open the Stackage web page with the resolver specified in your
stack.yaml and search for the library you’re interested in. For example, here we need
You can also do this through the terminal. For instance, for the
random library we can run:
$ stack ls dependencies | grep random
Sometimes a library might not be in the specified resolver. In that case, you need to add the library and its version to the
extra-deps field inside
stack.yaml. See example here.
Stack: REPL and executables🔗
You can use
stack repl command to launch the
ghci inside your project.
Use the following command to run a specific executable stanza:
$ stack exec name-of-my-executable
In conclusion, I want to say that workflows for
stack might look very similar, but the ideas behind them are different. I recommend you to try both before sticking to one tool. And I hope the instructions above will help you to get started with creating Haskell projects!
Useful addition to
stack in terms of building tools is
nix. Nix is also a very popular choice but it’s not yet beginner friendly. If you are interested in knowing more about how to apply
nix to Haskell, I suggest you read this tutorial:
If you’re interested in more experimental ways to build Haskell packages, you can check the following repositories: