loading...
Cover image for Haskell for madmen: Setup

Haskell for madmen: Setup

drbearhands profile image DrBearhands Originally published at drbearhands.com ・3 min read

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.

We're also going to need Docker. 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. 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. 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:

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.

stack run

3. Initialize the repository

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

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:

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.

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.

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/

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.

Posted on by:

Discussion

markdown guide
 

Thanks' for putting this together. I've glanced over post 2 as well, which I'm working on now. I hit a block when it came to the custom docker environment though:

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

Does this mean I need to register a new image called that on docker hub? Making a file at haskelltutorial/dockerfiles/buildenv doesn't seem to be doing the trick.

2) GitLab Ci now prompts me to enter some key value pairs and manually trigger the build. What key values is it expecting?

 

Hi Dave,

You don't need to use your own container image at all, if you're not familiar with docker and Gitlab CI it's better to skip that step and use the default image. Learn one thing at a time ;-)

The reason gitlab CI/docker explanations are even in there is that it can be tricky to setup a Haskell pipeline "correctly". If you have no caches, builds are going to take a looooong time. But there are better people to learn those technologies from.

 

Great that you're continuing the series! This post has some funky formatting, random line breaks all over the text which really hurts readability.

 

Also you can actually mark posts as belonging to a series, which gives you a nice list with links to the older posts at the top. It's an attribute of the form series: post series name in the frontmatter, i.e. series: Haskell for madmen.

 

Ai, bugger, looks like dev.to and pandoc handle markdown to html conversion differently. Series only works once I have multiple entries ;-)

I keep running into this - makes me sad.

We need a markdown to markdown converter!

 

For ligatures, using FiraCode (font) suffices

 

That is one way to do it, but I believe the IDE must still support ligatures.