This page is the beginning of the Applied Haskell course. For more information, see the syllabus. The goal here is to provide some basic information on:
Applied Haskell is a course developed over the years by the FP Complete team. It focuses on bridging the gap between Haskell basics to the ability to write production-grade Haskell applications. In our experience, there are a few important pieces necessary to make this happen:
This course will not teach you every detail of every library. But hopefully by the end you’ll be able to find any library you need, read its documentation, and use it.
Haskell is in many ways a revolutionary language. It was innovative when it first came on the scene decades ago, and remains innovative today. Haskell continues to add cutting-edge features, especially at the type level, to an industrial-strength language.
We don’t care about that here. In Applied Haskell, we’re going to treat Haskell as a "getting things done" language. To borrow phrase, we’re going to be "brutally pragmatic." Haskell delivers a huge amount of value with a subset of its features. We’re going to focus on those features and the tools and libraries necessary to take advantage of them. We’ll also be covering Haskell idioms and patterns so you can pick up common code more quickly, and discuss some runtime system idiosyncrasies.
You should know the basics of Haskell to follow along properly. You do not need to know advanced language theory, category theory, or any other surprising topics.
This course will assume that you’ll be using the Stack build tool. Working with other tooling like Cabal or Nix will work as well, but may require more fiddling. For following this course, it’s recommended to use Stack to avoid wasting time.
Stack is a build tool for Haskell. It can also install your compiler toolchain (and does so by default). It focuses on reproducible build plans. As mentioned, there are other tools, but we’ll be using Stack. It comes first in this list as it’s the first one you should install, by following the get started instructions.
If you already have Stack on your machine, you can usually upgrade to
the latest version by running stack upgrade
. Now that you have
Stack, we can move on to…
GHC is the de facto standard Haskell compiler. You can install a compiler version by running a command line:
$ stack setup ghc-8.4.4
Typically you won’t have to do this explicitly. When Stack notices you’re missing a GHC version, it will install it for you.
GHC can both compile code and run it interpreted, as well as provide
you with an interactive REPL. You can do those directly with,
respectively, the ghc
, runghc
, and ghci
executables. Stack does
not place these executables on the user PATH, so if you’d like to run
them, you’ll typically use stack ghc
, stack runghc
, and stack ghci
.
If you’d like more information on interacting in these three ways, check out:
The lessons in this course mostly stick to using the script approach.
Apologies in advance, the terminology is about to get a bit complicated.
Stack is built on top of the Cabal build system. When you install a
package with Stack (e.g., by running stack build conduit
), you’re
installing a Cabal package. Cabal refers to a few different things:
.cabal
files, which provide metadata on a
packageCabal
cabal
, and the
package it comes from is called cabal-install
.When using Stack, you’ll use the stack
executable instead of the
cabal
/cabal-install
executable. Other than that, you’re using the
same Cabal components. Both tools (and Nix, for that matter) can
install the same packages, so there’s high overlap.
The one final complication is that there’s another file format called
hpack. This file format is YAML based (with files called
package.yaml
), and is used to generate .cabal
files. Stack
supports these out of the box, and many Stack projects will provide
package.yaml
files, and have auto-generated .cabal
files.
More information on all of this at:
Note that these are more advanced issues which you probably won’t run into immediately. It’s good to know where more information is when you do hit a roadblock.
There are thousands of Haskell packages available online on Hackage, the central open source package repository. In order to make it easier to find a working build plan, and provide consistent behavior for multiple users, the Stackage project provides curated snapshots of packages which are tested to work together. There are two different sets of Stackage snapshots:
stackage.org provides a few resources. Some notable ones:
map
.Note that Hackage also provides API documentation. I recommend using Stackage’s in general for two reasons:
The hlint
tool is a great linter. It not only provides ways to
improve your code, but in the process can teach you about better ways
of writing code you weren’t aware of. Install hlint
with:
$ stack install --resolver lts hlint
To see an example of hlint
‘s power, save the following to Main.hs
and then run hlint Main.hs
:
main :: IO ()
main = do
mapM putStrLn ["Hello", "World"]
pure ()
Unfortunately in the Haskell world, code formatting isn’t quite at the level of rigour as, say, Go. There are different coding styles that are commonly used, and (at least to my knowledge at time of writing) none of the tools can be guaranteed to perfectly format every valid Haskell program. Two commonly used tools are:
There are lots of different pieces of integration for lots of editors, with various IDE support. This is a topic that’s often in flux, depending on which tools have upgraded to the most recent release of GHC. For learning, I recommend: stick to simple syntax highlighting. If you find integrations that work well for you, great! The unfortunate reality is that what works for one person doesn’t reliably work for others.
One tool which I will mention is ghcid. It is fully usable from the command line (in fact, that’s its intended use case), and provides fast auto-rebuild support using the GHC interpreter. Basically:
stack install --resolver lts ghcid
ghcid
There are many resources available for Haskell across the web. In addition to some of the links above:
Before diving into the rest of this course, a quick recap of some nomenclature to be familiar with:
type String = [Char]
newtype Age = Age Int
or newtype Age = Age { unAge :: Int }
data Person = Person Name Age
data Person = Person { personName :: Name, personAge :: Age }
data Fruit = Apple | Banana | Pear
data Age = UnknownAge | KnownAge Int
Terms:
Fruit
is a type constructorFruit
is also a typeApple
, Banana
, and Pear
are data constructorsPerson
is both a type and data constructor
Type variables:
data Maybe a = Nothing | Just a
a
is a type variableMaybe
is a type constructorMaybe
is not a type!Maybe
has kind Type -> Type
, aka * -> *
Maybe Int
is a typeMore on data types in next section.
Bottom value: when evaluated, throws a runtime exception or loops infinitely
bottom1 = error "I'm bottom!"
bottom2 = undefined
bottom3 = _ -- probably fails at compile time
bottom4 = let x = x in x -- infinite loop, runtime may detect it
Total function produces a non-bottom output for all non-bottom input.
Partial function may produce a bottom output for some non-bottom input.
Partiality can sneak in:
-Wall
!50% of all production Haskell code is language extension and import lines
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GADTs #-}
Added at the top of your file. Basic structure:
#!/usr/bin/env stack
-- stack --resolver lts-12.21 script
-- Above two lines for scripts, we'll cover that below
{-# LANGUAGE OverloadedStrings #-}
-- {-# LANGUAGE ... #-}
{-# OPTIONS_GHC -Wall #-} -- better in package.yaml file, below
module Main (main) where
import Control.Monad (when)
-- import ...
main :: IO ()
main = putStrLn "Finally, some actual code!"
See: https://github.com/commercialhaskell/rio#language-extensions
Function calls:
foo x y = ...
foo = x y -> ...
foo = x -> y -> ...
All the same, allow for partial function application (not the same as partial functions!).
Pattern matching:
fromMaybe def Nothing = def
fromMaybe _def (Just x) = x
fromMaybe def mx =
case mx of
Nothing -> def
Just x -> x
{-# LANGUAGE LambdaCase #-}
fromMaybe def = case
Nothing -> def
Just x -> x
Also:
if x then y else z
case x of
True -> y
False -> z
Subscribe to our blog via email
Email subscriptions come from our Atom feed and are handled by Blogtrottr. You will only receive notifications of blog posts, and can unsubscribe any time.