Haskell for madmen: Setup
Tags: haskell, tutorial
Published: 2019-09-09 07:27:31

---
title: Haskell for madmen: Setup
published: true
tags: #Haskell, #tutorial
series: Haskell for madmen
cover_image: https://thepracticaldev.s3.amazonaws.com/i/u90q8rnyiz6609avk97k.jpg
canonical_url: http://drbearhands.com/haskell-tutorial/1.setup/
---

Before we start writing any code, let's ensure our environment is setup
properly.

### 1. Install the dependencies

The easiest way to build Haskell code and manage dependencies is by using
[Stack](https://docs.haskellstack.org/en/stable/README/).

We're also going to need [Docker](https://www.docker.com/get-started). This
isn't strictly necessary, but it's going to make a few things a lot easier, so I
will be using it rather extensively.

Finally, I'm going to demonstrate setting up your build pipeline on
[Gitlab](https://gitlab.com). You can use other repositories and CI/CD tools,
but you'll have to adapt the knowledge in this tutorial yourself.

I will henceforth assume you've successfully installed stack, docker and
[git](https://git-scm.com/). You may need to install some additional
dependencies along the way, but that's rare for consumer systems.

### 2. Create the project

Let's test you've installed stack correctly. Run this in a console:

```sh
stack new haskell-tutorial
cd haskell-tutorial
```

And run the project for the first time, this will build the project's
dependencies, including the compiler, and might take a while the first time.

```sh
stack run
```

### 3. Initialize the repository

Create a new repository on gitlab, then initialize your repository locally and
make your first commit:

```sh
git init
git remote add origin <your repository URL>
git add .
git commit -m"initial commit"
git push -u origin master
```

### 4. Setup CI/CD pipeline

Because Haskell has such powerful static checks, it makes a lot of sense to have
a CI pipeline even if you don't do a thing with the results.

Create a `.gitlab-ci.yml` file and open it with your editor of choice.

A basic CI pipeline that caches our dependencies looks like this:

```yaml
stages:
  - build

variables:
  DOCKER_DRIVER: overlay2
  STACK_ROOT: ${CI_PROJECT_DIR}/.stack

build-backend:
  stage: build
  image: haskell:latest
  cache:
    paths:
      - ${STACK_ROOT}
      - .stack-work
  script:
    - stack
      --stack-root ${STACK_ROOT}
      build
```

You could push this configuration to Gitlab and it will build your project. The
first run will take a while as stack downloads and install ghc, the compiler.

#### 4.1 Basing on the alpine image (optional)

You might (like me) not like using the `haskell` image, and rather roll your
own, based on alpine. This is a bit more involved and only for those already
familiar with gitlab and docker.

First, we're going to make our own container image for building our Haskell
application. Let's call it `dockerfiles/buildenv`.

```dockerfile
FROM alpine:latest

ENV PATH ${PATH}:/root/.cabal/bin:/root/.local/bin

RUN apk add --no-cache ghc curl musl-dev zlib-dev postgresql-dev
RUN curl -sSL https://get.haskellstack.org/ | sh
```

And let's build and use it in our CI pipeline.

```yaml
stages:
  - build-requirements
  - build

variables:
  DOCKER_DRIVER: overlay2
  BUILD_ENV_IMAGE_TAG: ${CI_REGISTRY_IMAGE}/buildenv
  BUILD_ENV_IMAGE_TAG_VERSIONED: ${BUILD_ENV_IMAGE_TAG}:${CI_COMMIT_SHA}
  STACK_ROOT: ${CI_PROJECT_DIR}/.stack

build-environment:
  stage: build-requirements
  when: manual
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} registry.gitlab.com
    - docker build -f dockerfiles/buildenv -t ${BUILD_ENV_IMAGE_TAG} .
    - docker tag ${BUILD_ENV_IMAGE_TAG} ${BUILD_ENV_IMAGE_TAG_VERSIONED}
    #push twice, both latest and versioned tags
    - docker push ${BUILD_ENV_IMAGE_TAG}
    - docker push ${BUILD_ENV_IMAGE_TAG_VERSIONED}

build-backend:
  stage: build
  image: ${BUILD_ENV_IMAGE_TAG}:latest
  cache:
    paths:
      - ${STACK_ROOT}
      - .stack-work
  script:
    - stack
      --stack-root ${STACK_ROOT}
      build
      --system-ghc
```

It is quite likely you've now run into an error that looks something like this:

```
No setup information found for ghc-8.6.5 on your platform.
This probably means a GHC bindist has not yet been added for OS key 'linux64-ncurses6'.
Supported versions: ghc-7.10.3, ghc-8.0.1, ghc-8.0.2, ghc-8.2.1, ghc-8.2.2
```

This is because the file `stack.yaml` specifies which resolver (the list of
stack-curated dependencies) to use, which in turn determines the version of ghc
we require, and that version just has not been ported to alpine yet. However,
the versions mentioned in the error message are *not* the ones we can use, as
we're using --system-ghc, the right version should be listed in the logs of the
CI job creating your build environment. In my case:

```
(25/37) Installing ncurses-terminfo-base (6.1_p20190518-r0)
(26/37) Installing ncurses-terminfo (6.1_p20190518-r0)
(27/37) Installing ncurses-libs (6.1_p20190518-r0)
(28/37) Installing ghc (8.4.3-r0)
```

I will need to speficy a resolver using ghc-8.4.3. So, in `stack.yaml`, I changed
`resolver: lts-13.27` to `resolver: ghc-8.4.3`.
You can find a list of resolvers at [https://www.stackage.org/](https://www.stackage.org/)


# 5. Ligatures

I'm not going to cover IDEs, as that is a hotly debated topic that I have no
interest getting into. However, I've found that using ligatures helps a lot with
code readability in Haskell, so I would recommend using an IDE that supports
them.