This blog post describes the two main ways of managing and building Haskell projects: using the cabal
and 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.
Introductionπ
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 cabal
and 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:
cabal-install
- Documentation: https://www.haskell.org/cabal/users-guide/
stack
- 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π
The word cabal
in Haskell is semantically overloaded. In Haskell, cabal
can refer to:
cabal-install
- The build tool for Haskell projects.
- Format of files with extension
.cabal
- The configuration (modules, dependencies, metadata) of a package
foo
must be written in a file calledfoo.cabal
with a special syntax that can be read bycabal-install
.
- The configuration (modules, dependencies, metadata) of a package
- Haskell library
Cabal
- This is a library used by
cabal-install
that implements a parser for the.cabal
file format.
- 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.
Cabal Installationπ
Cabal: Ubuntuπ
The latest cabal
and ghc
can both be installed through hvr/ghc
PPA:
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
cabal
HEAD since it has better support for the commands we will use to build projects withcabal
.
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 2.5.0.0
compiled using version 2.5.0.0 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π
To get cabal
for Mac Os you can just use brew
:
$ 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:
Cabal: Windowsπ
The easiest way to install cabal
and 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:
cabal initπ
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
tool. 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 summoner
usage.
Cabal project structureπ
If youβve chosen to create cabal-example
with 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: kovanikov@gmail.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:
module Dummy where
inc :: Int -> Int
= x + 1 inc x
After that, letβs replace this line inside cabal-example.cabal
:
-- exposed-modules:
with:
exposed-modules: Dummy
to tell 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
~/.cabal
directory. - You want to use a newer version of a library that has become available after you last executed
cabal new-update
.
NOTE: If something goes weirdly wrong when building dependencies (like scary linker errors) then deleting the
~/.cabal
directory might help.
Finally, to actually build the package, run the command:
$ cabal new-build
IMPORTANT NOTE:
cabal
also has abuild
andupdate
command without thenew-
prefix. Donβt use them! Use the commands with thenew-
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 thenew-
prefixed commands). Old commands will be replaced by new ones incabal-3.0
and 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!
module Dummy where
import System.Random (randomRIO)
inc :: Int -> Int
= x + 1
inc x
dice :: IO Int
= randomRIO (1, 6) dice
Run 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
base
package in your.cabal
file. If you have a lot of packages without explicit version boundaries,cabal
might not be able to build your project, because it wonβt know which versions of the packages to pick.
cabal replπ
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:
> inc 64
ghci65
> dice
ghci2
> dice
ghci1
> :q
ghciLeaving GHCi.
$
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 .cabal
file.
NOTE: Top-level sections like
library
,executable
and 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 .cabal
file:
-exe
executable my-is: Main.hs
main-depends: base, cabal-example
build-language: Haskell2010 default
NOTE: We add
cabal-example
tobuild-depends
to be able to use functions from ourlibrary
stanza. No need to specify version bounds forbase
here since they are derived from thecabal-example
dependency.
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:
module Main where
import Dummy (dice)
main :: IO ()
= do
main putStrLn "777"
<- dice
n print n
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:
Stackπ
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.
Stack: Installationπ
You can find instructions for how to build stack
at the beginning of the stack
documentation:
You donβt need to install ghc
separately since stack
will download a suitable compiler version for you.
Stack: Unixπ
Installing stack
on any unix system is really easy:
$ curl -sSL https://get.haskellstack.org/ | sh
Stack: Windowsπ
For Windows, you can download the official binary directly from the website:
stack newπ
To create a new project named stack-example
you run:
$ stack new stack-example
NOTE: To successfully finish the project initialization with
stack
your working machine should have an internet connection since offline mode has not been implemented yet.
Unlike 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 library
, executable
and test-suite
stanzas added to the .cabal
file with the corresponding Haskell code being put into the src/
, app/
and test/
directories respectively.
The stack.yaml
file contains some extra configuration for stack
.
The 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 package.yaml
file.
Building stack projectπ
Use the following command to build the project including library
, executable
and test-suite
stanzas, and run tests as well.
$ stack build --test
stack
will automatically download the proper GHC version during your first build.
LTS resolverπ
One of the main differences between stack
and 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:
: lts-13.2 resolver
NOTE: This is the only line in
stack.yaml
that is required for stack to work in most cases. The defaultstack.yaml
file 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 build-depends
field.
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
Conclusionπ
In conclusion, I want to say that workflows for cabal
and 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!
Otherπ
Useful addition to cabal
and 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: