📹 Hate reading articles? Check out the complementary video, which covers the same content: https://youtu.be/c7FncTzvpUQ
Haskell is covered with stereotypes and myths. And one of them is the idea that there exists a “Tooling issue” (with a capital T).
Maybe it was true back in the day. But anyways, I want to cover the current state of things and show some tooling that other language ecosystems can only dream about.
And, of course, there are some issues here and there; we’re all software engineers, and we know how it is.
We’re going to cover the following:
- Installation tools
- Build tools
- IDE / Language server
- Cherry on the top
🌚 Note that we’re not going to talk about Nix.
Speed-run
Today’s standard is to install GHCup, then use it to install the compiler (GHC), the build tools (Cabal, Stack, or both), and the language server (HLS). Afterward, you install VSCode with the Haskell extension (or your favorite editor), and you’re ready.
GHCup – the main Haskell installer.
GHC (Glasgow Haskell Compiler) – the compiler.
Cabal – a Haskell build tool.
Stack – alternative Haskell build tool (based on snapshots).
HLS (Haskell Language Server) – Haskell LSP support.
And this is precisely what we’re going to do.
Note that I’m super lazy; this is almost the setup I’m using. The only configuration I ever do is install another font. But you can take it way further than that.
Installation
We can get all we need with one single tool, GHCup. It manages the main Haskell tools: GHC, Cabal, Stack, and HLS. We don’t need to install or manage any of these by hand.
💡 Fun fact: GHCup has nothing to do with cups.
After you install GHCup for the first time, you should have a working Haskell stack on your machine.
You can use the command line interface to upgrade/update the tools; for example, install the recommended version of the language server (hls
):
ghcup install hls
But you can also use the tui (text-based user interface):
ghcup tui
You can see from the screenshot which hls
versions I have installed and that I also have multiple versions of ghc
installed for different projects. And it’s super easy to jump between the versions. I don’t even think about it.
Build tools
There are two main build tools in Haskell: Cabal and Stack. Over the years, their popularity keeps shiftings, and their functionality keeps converging. If you're getting started, you can use either one.
One difference is that Stack uses the curated set of packages by default, while it must be configured in Cabal.
Stackage is a community project that curates these sets and bundles the dependencies known to build together, avoiding any version conflict problems. Which is very appealing. Especially if you have ever experienced ClassNotFoundException
at runtime, had to shovel yourself out of the conflicting guava
dependencies in Java, or something along these lines.
💡 PureScript has a similar project, called Registry.
Because I’m lazy and want to illustrate it quickly, I’ll show you Stack.
We can initialize and run a project (named, for example, dry-run
) with these commands:
stack new dry-run
cd dry-run
stack run
Which will probably print:
someFunc
🙂 You can imagine that it says: “Hello, World.”
Now, let’s try to change the code.
IDE
We only need HLS (which we already have after installing GHCup) and VSCode with the Haskell extension.
And now we have a fully working IDE.
💡 It doesn’t have to be VSCode! You can use your favorite editor with HLS.
HLS provides go-to-definitions, autocompletion, and all the other stuff, which we can use to find the someFunc
, change it, and print something else:
someFunc :: IO ()
someFunc = print 42
And then, we can run the project via the terminal.
Now, let’s add a dependency. For example, let’s add aeson
– a library for working with JSON. We need to extend the dependencies
list in the package.yaml
:
dependencies:
- base >= 4.7 && < 5
- aeson
And we don’t need to worry about the library version, the resolver handles the versions for us – it chooses a specific Stackage snapshot. We can check it in the stack.yaml
:
resolver:
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/11.yaml
And the cool part: if we want to add and use another library, which is also an aeson
dependency, for instance, text
, we know that the version we utilize in the project is compatible with the version that is required for aeson
.
As an illustration, we can use this library to encode a list as JSON. Note that we can use auto-import.
import Data.Aeson (encode)
someFunc :: IO ()
someFunc = print (encode ["Hello", "World"])
-- Prints: "[\"Hello\",\"World\"]"
Hoogle
Have you ever forgotten a name of a function? Or how many minutes have you spent scrolling through the autocomplete suggestion list, searching for a method that fits? Or how often do you have to jump between library docs to find that data structure you need?
What if there was a type-aware search engine that you could ask?
Well, we have this tool in Haskell, and it’s called Hoogle
. We can use it to search Haskell libraries by name and type signature.
💡 We also have such tool in PureScript, which is called Pursuit
.
Okay, are you ready? Let’s do a couple of searches.
Search for a text
Sometimes, I have to use a function to add a value between the elements of the list. For example, add a comma between the list of words. But I struggle to remember its name – it’s either intersperse
, intercalate
, or inter-
whatever.
I can use Hoogle to search for it:
inter
It shows the type signatures and the docs, so I remember it was intersperse
once again.
It’s right there, closer to the bottom:
intersperse :: a -> [a] -> [a]
Okay, nothing crazy; it's as impressive as autocomplete. But!
Search for a type
But we can also search for a type right away.
Let’s start from scratch. What do we need from this function? We need to pass an element and a list (the types of the old and new elements should be the same), and the result should be a new list. So, we can search for it:
element -> [element] -> [element]
It’s right here, on the top:
We can use simpler types to search:
a -> [a] -> [a]
It doesn’t change the result:
Or, imagine we’re looking for a function that checks if the element exists in the list. This time, we change the return type to boolean:
a -> [a] -> Bool
And get something like this:
Which gives us elem
and it’s negative twin notElem
.
Note that we used a polymorphic type a
because we wanted a function that works for any list, but we can search for a more specific type as well:
Int -> [Int] -> Bool
In this case, Hoogle first suggests more specific functions and then generic ones like elem
.
And it works with the whole stackage set (or even beyond it) – we can use it whenever we’re stuck looking for a suitable function. If this isn’t cool, I don’t know what is.
💡 Note: You can also install Hoogle locally – run searches on a command line or in a browser, and even hook it up to your editor.
Conclusion
So, yeah, there is no “Tooling Issue” in Haskell. You install one tool and one editor and have all the autocompletes, auto-imports, goto-definitions, etc.
Also, I want to thank everyone who is working on the tooling. You’re the real heroes.
The tools are improving daily, and I’m curious if this walkthrough will age well.