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.
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, 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
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
:r to do the same thing.
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.
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:
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.
NOTE: This post was a draft for quite a while, and since then I no longer use Hindent, I use Brittany.
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.
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.
Num t => t means when using
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
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
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
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
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
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.