DEV Community

Cover image for Haskell for madmen: Setup
DrBearhands
DrBearhands

Posted on • Originally published at drbearhands.com

Haskell for madmen: 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.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (9)

Collapse
daveparr profile image
Dave Parr

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?

Collapse
drbearhands profile image
DrBearhands Author

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.

Collapse
citizen428 profile image
Michael Kohl

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

Collapse
citizen428 profile image
Michael Kohl

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.

Collapse
drbearhands profile image
DrBearhands Author

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

Thread Thread
gypsydave5 profile image
David Wickes

I keep running into this - makes me sad.

Thread Thread
drbearhands profile image
DrBearhands Author

We need a markdown to markdown converter!

Collapse
gnsp profile image
Ganesh Prasad

For ligatures, using FiraCode (font) suffices

Collapse
drbearhands profile image
DrBearhands Author

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