February 25, 2021

2159 words 11 mins read

The Haskell FAQ

This is the proper introduction that I didn’t have when starting out with Haskell, I hope it serves you well. Let’s go!

Ok, now you’re just getting started, but there are many questions that come to mind when getting started with Haskell and seems no other resources make mention of it, don’t worry, here we’ll review most of the common doubts you could have not only about the language but about the tooling or ecosystem. If any of your questions does not appear in the list just head to /r/haskellquestions I have found it a very approachable community.

What is Stack and Cabal?

As mentioned above, these are build tools that overlap and complement each other. Stack uses Cabal under the hood, it exists only to ease the management of Haskell projects and their dependencies, testing and benchmarking of the code, but Cabal can do that as well but you have to be in more familiarity with the way everything fits or else you’ll end up with a messed up system. Advanced Haskellers often use Nix + Cabal to manage their projects, but this is complex, nowadays Cabal supports Nix-style local builds, that is a sandbox that eases up management of different projects and their dependencies. Stack is most used in industrial grade projects that uses Haskell in production, but you can achieve such reliability (and more) with Nix if you know your way through it.

Perhaps is a bad analogy but you could think of Cabal as the Haskell’s NPM, and Stack as Haskell’s Yarn, not quite true because NPM is very nasty, but it’s easier to understand them that way.

What is ghc, ghci, ghcid and ghcide?

GHC

You already know ghc is the compiler, you can invoke ghc from the command line directly if you have it installed system wide, if you’re using stack you need to do stack ghc instead.

GHCi

GHCi, is the interactive REPL, like the one you have when you just type python and just start an interactive code session, this is very good taking into account that Haskell is compiled, when using the interactive mode your Haskell code is loaded very fast. To start an interactive session just use ghci or if using stack then use stack ghci.

One of the most useful commands you can use while in the interactive session is :type to inspect the type of a given expression:

Prelude> :type False
False :: Bool
Prelude> :type 'A'
'A' :: Char
Prelude> :type "hello"
"hello" :: [Char]
Prelude> :type 42
42 :: Num p => p

Other useful ones are :info to get info about something, :load to load a Haskell file, and :reload to reload when you make changes to a file that was loaded into the session, most of the commands can be used using shortcuts such as :t, :i, :l and :r to do the same thing.

GHCid

This is a watcher on your Haskell project, you keep it running in a terminal and you just edit your code and instantaneously give you any errors you might have, you can configure it to run your tests automatically or run your app, basically it has automated loading & reloading code into GHCi and it is very useful to speed up the developer’s feedback-loop while coding Haskell. It doesn’t come installed by default, you have to refer to the site to see how you can install it.

GHCide

The same creator for ghcid worked of an enterprise that set to build a editor independent IDE for Haskell, ghcide is a LSP server to give you intellisense about Haskell code right in your preferred code editor. As of today it isn’t recommended using it directly since now HLS (Haskell Language Server) uses it as it’s core, a LSP server with more features.

How to setup a Haskell for development?

There are many variations and there isn’t one that is the “defacto” way to setup a Haskell development environment, most of what I heard is on VSCode, Vim and Emacs. Right now I think most is converging to use HLS and this is what I use on Emacs:

(use-package lsp-haskell
  :after lsp
  :demand t
  :custom
  (lsp-haskell-formatting-provider "brittany"))

bryttany is just a code formatter for Haskell (but there are others). Also before using HLS I was using haskell-mode, this was my config back then:

(use-package haskell-mode
  :config
  (let ((stack-command "stack build --pedantic --fast"))
    (setq haskell-stylish-on-save t
          haskell-compile-cabal-build-command (concat "cd %s && " stack-command)
          projectile-project-compilation-cmd stack-command
          projectile-project-test-cmd (concat stack-command " --test")
          flycheck-ghc-language-extensions '("OverloadedStrings"
                                             "NamedFieldPuns"
                                             "FlexibleInstances"
                                             "FlexibleContexts")))
  :general
  (:keymaps 'haskell-mode-map :states 'normal :prefix "SPC"
   "rr" 'haskell-compile
   "ra" 'projectile-compile-project
   "rt" 'projectile-test-project))

You can notice I also use projectile and flycheck, these are useful Emacs plugins to manage projects and report errors in my code. If you use this setup you may also find helpful the following Haskell specific tools:

Hlint

It’s an advanced linter tool, it makes suggestions on your code so it can be more concise and readable, it also detects duplicated code and suggest refactors one could use instead, this is a command line utility but in Emacs flycheck uses it by default, other editors may use it as well.

Hindent

Is a Haskell code formatter, there are several more “modern” alternatives like Brittany and Ormolu, but I’m comfortable with this one and haven’t tried any of those yet.

NOTE: This post was a draft for quite a while, and since then I no longer use Hindent, I use Brittany.

Stylish Haskell

Is a Haskell code beautifier, it mainly helps reorganizing compiler pragmas, imports, aligning record definitions, etc. You may find it to be a nice complement to Hindent.

Ghcid

As mentioned this tool is amazing, if you don’t use this you will have to change your code, then compile, then run to see if it worked, with Ghcid you just keep it running and as soon as you save your changes it will recompile very fast and tell you any errors you might have. You can set it up to run your tests on successful compilation and to run the app after that. You configure it via command line flags or by using a .ghcid file in your project.

What does Num t => t means when using :type?

When I was starting out with Haskell I had this question, I understood why :type False yielded Bool, but I couldn’t understand why :type 42 not yielded Int, instead we see something like Num t => t (instead of t you might have other letter). What this means is that t is the type of 42, but t means can be any type, it’s like a variable but in the types realm, it can refer to any type, but that would mean that if t is Bool (since Bool is a type) it would mean that 42 can be a Bool and that doesn’t make any sense (unless you come from other languages that casts integers to booleans when needed, Haskell doesn’t do that), so the type t can be any type, but it has a constraint, that’s what you call the thing before the fat arrow =>, the constraint says that the type t must be a member of the Num type class, that type class mandates that the type that implements it must have the ability to be added, multiplied, subtracted, negated, among other things, so what 42 :: Num t => t means that 42 is of any type t that can be treated as a Num. This means that numbers in Haskell are polymorphic, could be Int, Integer, Double, Rational, etc., all of these are concrete types that implement the Num type class.

Here is a post on numeric type classes available in Haskell and in essence how numbers are organized.

How to debug a variable, how to print it’s value to see if it holds the expected thing?

In other languages I was accustomed to put some print instructions here and there to assert that my variables held the correct values, it turns out in Haskell you almost don’t need to do this kind of (naive) “debugging technique” since the language protects you so much, first, once a value is assigned to an identifier it can’t be changed (but it can be shadowed and Haskell will warn you) so there’s no need to print values of variables, variables are more like constants, they never change. Second, you can only use print-like instructions in special parts of your code, in Haskell you can’t do IO wherever you please, you must “mark” your functions to allow them execute IO functions. Like in Java you will use “throws” to signal a function could throw an exception, here you can’t do “print” if you don’t tag your function appropriately.

It only make sense to “print” something when “testing” the proper behavior of a function that performs a transformation on its input, and for this you can just use your function in the interactive REPL (GHCi). It doesn’t make sense to try to “print” the variable because you might suspect “it doesn’t have a value”, in Haskell everything has a value and when this does not hold you don’t get null you get an exception. If you have a function that expects a String and outputs an Int (String -> Int) then you can be certain, what ever entered your function is a String and nothing else, not null, not nothing, otherwise the code will not even compile.

Lastly, no one never told me that it is possible to print values of variables whenever you please by using a hackish function, as such, it is not recommended to use in you codebase because it breaks desired properties of the functional paradigm that Haskell preaches. There’s a Debug.Trace module that contains the trace function, this function has the signature String -> a -> a, it takes a message you want to see and any value you’re interested in, and it will just give the value back but you’ll see your message printed whenever the value is evaluated. This function as you see, performs a side-effect, performs IO but it’s signature is not “marked” as proper Haskell requires. Please don’t abuse this, don’t rely on it, otherwise you’re better off using other languages but Haskell, you won’t see its benefits if you don’t adhere to its style of programming. And even then your “debugging message” may not even execute when you think it will, due to Haskell evaluation being non-strict.

What’s the fuss about Monads?

I would say Haskell is very famous (or infamous) for the M word, first, it is great that Haskell grows popularity in the industry but I don’t think is great when its usually popular due to being difficult to learn, which in my opinion it isn’t, however learning the abstractions that Functional Programming enables such as Functors, Applicative Functors or Monads are quite challenging when first approached. But Monad is quite special, you see, Haskell is a purely functional programming language this means that all the functions you write are pure, they must be, otherwise the code doesn’t compile, what this means is that all functions must rely solely on it’s inputs and nothing else to compute, if you think about it, how is it possible one can read files? perform network requests? read the current time? handle random numbers? these are necessities of many programs but these are not pure functions. However, one can “wrap” these functions with a context, this is the IO monad, when doing these you’re no longer using simple values, you are using monadic values, values with a context and the next challenge is how do you compose functions that create these context values? it turns out the monad is the pattern that allow us to chain functions that perform this effects in sequence and the whole program remains pure!

I don’t think monads are popular due its utility in the language but instead by the challenge that some newcomers find hard to understand what does a monad even do. However this is quite easy to figure out once you implement some of the most common monads. The gotcha here, is that monad is just a context around a value, and a pattern that allows to compose functions which generates these “embellished” values. For example the number 2021 is an Int of course, but also the current year, so that number has a context (dates). Another example are “probabilities”, we know they’re float numbers that always are in the interval [0, 1], not matter what we do with probabilities we should always get a number within this range, thus one can define a probability monad in which addition of probability values just work without any extra work, the context holds the value and makes sure it can’t leave its context, so 0.5 + 0.8 would never be 1.3, probability of both events occurring should be 0.4, because we’re in the context of probability.