Setting up Haskell in VS Code on macOS

I love VS Code. I love how stripped down it is compared to “enterprise” IDE’s. I love how stripped up it is compared to emacs/vim and an assortment of command line windows. It’s fast, customizable, runs on your favorite brand of OS, be that Windows, macOS, Linux, even Docker containers. But most of all, I love how easy it is to integrate the language of your choice with the editor.

Search for an extension, click install, and away we go. I’ve been using it for Javascript, Elm, Purescript, ReasonML, F#, and Python development, all without a hitch.

And once you’ve become familiar using the editor with a language you already know, it’s trivial to start exploring a language you’ve always wanted to learn. So let’s learn a new language!

Quick Note

The previous version of this article documented setting up Haskell with Stack and the Haskell IDE Engine. This article has moved over to using Cabal and GHCID.

So Many Choices!

With a lot of languages, its fairly straight forward to figure out how to get started with VS Code. Type “python”, “c#”, “purescript” into the extensions marketplace search, choose the first extension in the list with a bazillion more downloads than the others, and you’re good to go.

Type “haskell” into the marketplace and you get the following not so obvious list.

Haskell is a language of many choices, the goal of this post is to wade through this myriad of options, to setup a reliable environment, that gives us many of the benefits of an IDE, so we can start exploring this really fun and unique language.

Up Up and Away

Choice starts right from the get go with Haskell, but we’ll use ghcup to get Haskell installed on our system.

What i like about ghcup is it‘s focused on doing one thing well, making it easy to install a specific version of GHC (Glasgow Haskell Compiler) on our system.

So let’s do a simple bootstrap of ghcup, ghc and cabal, using the following command in the terminal.

curl https://gitlab.haskell.org/haskell/ghcup/raw/master/bootstrap-haskell -sSf | sh

After hitting enter you’ll be prompted with the following note.

(Note: on OS X, in the course of running ghcup you will be given a dialog box to install the command line tools. Accept and the requirements will be installed for you. You will then need to run the command again.)

You can then either install the command line tools using these instructions, or you can let ghcup do it, upon which you’ll have to rerun the command.

You’ll now get prompted about adding the created environmental file to your bash shell. If your on a version of macOS before Catalina then choose yes, because bash used to be the default shell on macOS.

But with the introduction of Catalina, Bash is the shell of choice no longer, Catalina has switched out Bash for Zsh because of licensing fears.

So for Catalina users we’ll simply manually add the environmental file to our Zsh profile.

. "$HOME/.ghcup/env"
echo '. $HOME/.ghcup/env' >> "$HOME/.zshrc"

I also like adding VS Code to my zprofile so i can run it from the command line.

cat << EOF >> ~/.zprofile
# Add Visual Studio Code (code)
export PATH="\$PATH:/Applications/Visual Studio Code.app/Contents/Resources/app/bin"
EOF

Creating a Project

Now that we have ghc and cabal on our command line, we can create a project to get ourselves started.

mkdir myproject && cd myproject
cabal init -n --is-executable
cabal v2-run

You should see the following printed to the terminal.

Hello, Haskell!

Let’s open vs code and take a look at our newly created project.

code .

From Editor to IDE

The first plugin we’re going to install, to get that IDE goodness we’ve come to expect from languages running in VS Code, is ghcid. Read the following post for why gchid is “the most important tool for Haskell development environments”.

What i’ve come to appreciate about ghcid is that its fast and it works. The same can’t always be said about the other options out there. Let’s get ghcid running from the command line in the root of our project.

cabal update && cabal install ghcid
ghcid "--command=ghci Main.hs"

You should see the words “All Good” appear in the terminal window. Now let’s run gchid from the VS Code command palette. Running from the palette gives us integration with the problems tab and will display errors when hovering over red squiggles from the code editor.

cmd + shift +p bring up the pallette

Start Ghcid will launch it in the Terminal tab.

We’ll create s simple error by removing the quotation mark from the end of our main function in the Main.hs file.

As soon as we hit save, you’ll see the following in the problems tab

And hovering over the error in the code editor

Let’s fix our problem by adding back the missing quote and bang, we’re “All good” again. Fast, simple, and works! The holy trinity of sofware development.

Making Things Purdy

Now that we have the utility of an IDE out of the way (being told when we make a mistake at the time we make it), we can focus on making things purdy…err pretty for folks not acquainted with American colloquialisms.

I am a sad file of code :(

Yuck! Let’s spruce this poor file up a bit with some syntax highlighting.

Install the Haskell Syntax Highlighting plugin and our sad file has gotten a lot happier.

Adding Some Style

Let’s keep on happyizing our sad little file (I think that’s a word), the next extension we’ll install is the linter based on hlint, which is a package for source code suggestions to make our code simpler and more readable.

After installing the plugin you should get a warning about hlint not being found.

All we have to do is use cabal to install hlint, which unfortunately does take a long time.

cabal install hlint

Let’s add the following function to our Main.hs file to test out the linter.

doubleSecond :: Num t => [t] -> [t]
doubleSecond xs = [x | x <- zipWith (*) xs $ concat (repeat [1,2])]

The linting issue will show up in both the problems tab and as a red squiggle in the code editor.

HLint is letting us know we have a redundant list comprehension. So let’s click on the light bulb in the editor and choose the fix that hlint has suggested for us.

Much happier!

Debugging our Happy Mistakes

The last extension we’ll install, is that feature of IDE’s universally hated by “Crusty” old-school programmers, the trusty debugger.

First we’ll need to install the required packages.

cabal install phoityne-vscode haskell-dap

Once we have those installed, click on the debug icon on the left hand side of VS Code. Choose Add Configuration from the DEBUG drop down and choose haskell-debug-adapter.

The haskell-debug-adapter will generate a launch.json file which we’ll need to slightly modify as we’re using Cabal and not Stack.

"ghciCmd": "stack ghci --test --no-load --no-build --main-is TARGET --ghci-options -fprint-evld-with-show",

Will need to become

"ghciCmd": "cabal exec -- ghci -i${workspaceRoot}",

And we’ll need to change the location of our startup file.

"startup": "${workspaceRoot}/Main.hs",

So that are final launch.json file looks like the following.

Let’s set a breakpoint on the main function and start out debugger.

We can now step through code, inspect variables, break on conditions, get stack traces. All the things we expect from an IDE to help us find and fix problems in our code.

Haskell IDE Engine

I want to mention the Haskell IDE Engine here as its goal is to be the one stop shop for IDE features and basically replaces the plugins we have installed so far.

Haskell Language Server adds a number of features we expect from a modern development environment, type information, function definitions, jump to definition, case-splitting, etc.

The trick with haskell-ide-engine is that you have to install a version that matches with the version of GHC you are using in your project, the build times are long, long, long, and from time to time it just kind of stops working.

Here is the older version of this article, which goes over installing it with Stack.

The end product is quite nice though

so give it a try, you won’t need any of the plugins mentioned in the article as it really is a one stop shop.

The Haskell Tool Stack

In this tutorial we’ve used Cabal as our package manager and build tool, the other option is to use Stack. This article gives a great “opinionated guide” to using Stack as your package manager and build tool.

The main reason for choosing Cabal for this tutorial is there seems to be a trend in the Haskell community towards using NixOS as the package manager and Cabal as the build tool. Ghcup, which we used to install gch in the first place, is a direct result of this trend.

Other resources

I want to mention a few resoures that are invaluable in your exploration of the language. This site is awesome for experts and beginners alike.

And for a simple architecture to get you started on real world projects see this post.

And a standard library that implements these ideas.

Now We’re Ready to Learn Haskell

Much of the difficulty in learning a new language is simply setting up the environment and getting comfortable with the tools to start programming. Thankfully, VS Code let’s us use an editor we’re already comfortable with, so we can concentrate on learning syntax and concepts, instead of tools and workflows.

And so why should we learn Haskell in particular?

Becasue Haskell makes us better programmers. Haskell forces us to solve problems in different ways and forces us to be up front about the side effects we rely upon. At first this may be frustrating, or intimidating, but over time you’ll start finding the ways we used to solve problems (mutations, hidden side effects, iteration) are frustrating and intimidating. Have fun!

A programmer looking for ways to see the forest instead of the trees.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store