An opinionated list of resources for learning Haskell
CC0-1.0 License
| You might also be interested in my project-oriented online book: [[https://lhbg-book.link][Learn Haskell by building a static blog generator]] |
** About This Guide This guide is an opinionated list of resources for learning Haskell.
It is aimed at more experienced programmers that would like a denser Haskell tutorial.
If you prefer a gentler introduction, try one of these resources:
If you prefer videos:
And/or one of these resources to get started quickly:
** Table of Contents :TOC_3:
** Beginning
The basics are important, each resource here brings it's own view on it which will help solidify this material. If there are exercises to do, do them!
Key ideas:
In Haskell we use a different computation model
Referential Transparency enables equational reasoning
Types help prevent errors and help model programs ** More Basics Drill Down
Wikibook
[[https://www.haskell.org/hoogle/][Hoogle]]
[[https://github.com/ndmitchell/ghcid#readme][GHCid]]
Editor Integration
haskell
extension in vscode/vscodium.)[[https://www.ahri.net/practical-haskell-programs-from-scratch/][Practical Haskell programs from scratch - a quick and easy guide]]
[[https://sakshamsharma.com/2018/03/haskell-proj-struct/][Structuring your first Haskell project with Stack]] ** Useful Packages Here are a few useful packages you might want to use when building software with Haskell:
[[https://hackage.haskell.org/package/base][base]] - Haskell standard library. Contains large collection of useful libraries ranging from data structures to parsing combinators and debugging utilities.
[[https://hackage.haskell.org/package/containers][containers]] - Contains efficient general-purpose implementations of various immutable container types including sets, maps, sequences, trees, and graphs.
[[http://hackage.haskell.org/package/vector][vector]] - Efficient arrays.
[[https://hackage.haskell.org/package/text][text]] - An efficient unicode text type. It is much more efficient than the built in String type.
[[https://hackage.haskell.org/package/bytestring][bytestring]] - An efficient vector of byte type.
[[http://hackage.haskell.org/package/async][async]] - API for running IO operations asynchronously.
[[http://hackage.haskell.org/package/network][network]] - Low-level networking interface.
[[http://hackage.haskell.org/package/random][random]] - random number library.
Book (paid): [[https://leanpub.com/haskell-stdlibs/][Haskell (Almost) Standard Libraries]] by Alejandro Serrano Mena
[[https://hackage.haskell.org/][And more]]. ** Exercises *** Lists
Think of scenarios and test your functions. *** Dict Compress and decompress a file using dict compression.
Dict compression takes text, splits it by words, and creates two things:
Your task is to create an application that can either compress or decompress a text file.
There are two commands: compress and decompress, they both get a text file.
For the compress command, the output should be the compressed items ((1) and (2)). For the decompress command, the output should be the original text.
Note: You can use the functions read and show to convert from/to some types and String.
*** PPM
Create a program that will output a [[https://en.wikipedia.org/wiki/Netpbm_format#PPM_example][PPM file]].
Implement the following operations:
literal integers, +, -, *, /, negate
Example execution:
#+BEGIN_SRC $ rpn-calc 5 7 2 negate + * 25 #+END_SRC ** Lambda Calculus *** Overview The lambda calculus is a minimalistic language that is in the core of functional programming.
It presents a minimalistic framework to learn about many common features in functional languages.
While this section isn't strictly necessary, and you can skip it, it does provide some insight about the core of Haskell.
*** Exercises
Use this [[http://cdparks.github.io/lambda-machine/][Lambda Machine]] to check your answers
** Kinds *** Overview Every expression has a concrete type.
Kinds are the types of types.
This is a simplified view of how kinds are represented in GHC:
#+BEGIN_SRC haskell data Kind = Type -- can also be written as: * | KArr Kind Kind -- KArr in Haskell this is written as: -> #+END_SRC
Think of Type being the kind of concrete (or inhabited) types, and KArr is a function from Kind to Kind.
If a type is parametarized (when defining the ADT you pass it parameters) then in order for it to be concrete you have to supply it with all the types it expects to get.
Example:
#+BEGIN_SRC haskell
data Bool = True | False
data Maybe a = Just a | Nothing
#+END_SRC
Bool is not parametarized so it is a concrete type (which means it's kind is Type)
and has the Values True and False.
Maybe is not a concrete type, it need to be supplied with a type for a. (It has the kind Type -> Type).
Maybe Bool is a concrete type because all of the paramters for Maybe have been supplied.
An expression can only have a type with the kind Type.
Examples:
| Value | Type | Kind | Comments |
|-----------+------------------------+--------------------------------+--------------------------------------|
| True | Bool | Type (also written *) | a value |
| 'c' | Char | Type | |
| "Hello" | String | Type | |
| not True | Bool | Type | function application |
| Just True | Maybe Bool | Type | |
| ["Hello"] | [String] | Type | |
| Nothing | Maybe a | Type | polymorphic |
| id | a -> a | Type | a function |
| map | (a -> b) -> [a] -> [b] | Type | |
| map not | [Bool] -> [Bool] | Type | partially applied function |
| getLine | IO String | Type | |
| putStrLn | String -> IO () | Type | |
| | Void | Type | a concrete types with no values |
| | Maybe | Type -> Type | isn't fully supplied with parameters |
| | IO | Type -> Type | |
| | Either | Type -> Type -> Type | |
| | Either a | Type -> Type | partially supplied with parameters |
| | Free | (Type -> Type) -> Type -> Type | the first argument is of higher kind |
You can use ghci to query the kind of a type using :kind
Why do we care about Kinds? It let us generalize things and create abstractions.
Let's take a look at a data type that uses higher kinds:
#+BEGIN_SRC haskell data Rec f a = Rec a (f (Rec f a)) #+END_SRC
Why is this data type interesting? Let's try to plug some types and see.
We need some a which as kind Type so let's just choose Int for now, and let's use Maybe for f.
Let's look at some values of our new type Rec Maybe Int.
See a pattern here? it seems like this is an encoding of a non-empty list:
Let's take a look at another example with this type:
#+BEGIN_SRC haskell data Identity a = Identity a #+END_SRC
Identity basically just holds a value of type a. Nothing interesting here.
Let's try to plug it in Rec (and get Rec Identity Int) and see what kind of value we can have:
As you can see we basically need to keep providing new values with no way of bailing out. So we got an infinite list of values (or a stream).
We can write all kinds of generic algorithms on this data type and reuse them
for different scenarios and needs simply by pluging in a different f!
We'll see more of those after we talk about type classes.
There is more to Haskell's kinds system, and a really good article about it is linked later on the tutorial.
And by the way, the real name of Rec is [[https://hackage.haskell.org/package/free-5.1/docs/Control-Comonad-Cofree.html][Cofree]].
*** Exercise
Try to plug into our Rec a different type of kind Type -> Type that you know and see what happens!
** What is IO?
*** Overview
It is a parametarized type constructor (it has the kind Type -> Type).
IO a represents a description of a program (or subroutine) that when executed
will produce some value of type a and may do some I/O effects while at it.
Evaluating an IO a is pure - the evaluation will always reduce to the same description of a program.
In an executable, you need to define main :: IO () - a description of a program to run. The Haskell runtime will execute this.
You can combine subroutine descriptions to create bigger subroutine descriptions:
pure :: a -> IO a
Produces a value without doing any I/O.
Which has the type IO Bool, will not do any I/O and when executed will produce a value of type Bool, specifically True.
fmap :: (a -> b) -> IO a -> IO b
Similar to map on lists, it will apply a function on the parameter of IO.
Which has the type IO Bool will not do any I/O and when executed will produce a value of type Bool by first applying the function not on the result of pure True,
and so will produce the value False.
(>>) :: IO a -> IO b -> IO b
Run this first thing, discard the result, and then run the second thing.
Which has the type IO (), when executed, will print the string Hello and then will print the string World
and will produce a value of type (), specifically () (in this case the value has the same name as the type).
(>>=) :: IO a -> (a -> IO b) -> IO b
Run this first thing, take its result, pass it to the function which is the second argument, and then execute that.
Which has the type IO () will read a String from the user, apply that String to putStrLn and then execute it,
thus printing the same string it got from the user.
Then it will produce a value of type (), specifically ().
Note: You can implement (>>) using (>>=) like this:
#+BEGIN_SRC haskell (>>) prog1 prog2 = prog1 >>= _ -> prog2 #+END_SRC
join :: IO (IO a) -> IO a
Takes a description of a program that produces a description of a program that produces a value of type a
and converts it to a descrption of a program that will produce a value of type a by executing the first, and then executing the result.
Which is the same as getLine >>= putStrLn.
As you can see we can implement >>= using fmap and join
#+BEGIN_SRC haskell
(>>=) prog func = join (fmap func prog)
#+END_SRC
There are many more functions and combinators that return IO a. You can view some of them in the module [[http://hackage.haskell.org/package/base-4.11.1.0/docs/System-IO.html#t:IO][System.IO]].
*** Do notation
do notation is syntactic sugar around >> and >>=.
Example:
#+BEGIN_SRC haskell main = do putStrLn "Tell me your name." let greet name = "Hello, " ++ name ++ "!" name <- getLine putStrLn (greet name) #+END_SRC
Will be desugared to:
#+BEGIN_SRC haskell main = putStrLn "Tell me your name." >> let greet name = "Hello, " ++ name ++ "!" in getLine >>= \name -> putStrLn (greet name) #+END_SRC
This is basically CPS (continuation passing style).
| code | operator | type of the left side | type of the right side | comments |
|-------------------------+----------+-----------------------+------------------------+---------------------------------------------------------------------------------------------|
| let gretting = "hello" | = | String | String | = means both side are interchangeable (they both mean exactly the same thing) |
| let mygetline = getLine | = | IO String | IO String | Here we just create a new name that is identical to getLine. We are not running anything |
| name <- getLine | <- | String | IO String | <- is syntactic sugar for >>= where we bind the result of the computation to the name |
IO's API fits a pattern that can be seen in more types in Haskell, which is why the type signatures of the functions presented here are more general. We'll discuss that later. *** Exercises
A good type class will have operations on the type and laws attached to it - similar to abstract algebra.
Laws cannot be enforced by the compiler - a good convention in Haskell is not to define lawless type classes and not implement unlawful instances.
We define a type class like this:
#+BEGIN_SRC haskell class Eq (a :: *) where (==) :: a -> a -> Bool #+END_SRC
We define a class of types that can implement the operation (==).
We implement an instance of a type class for a given type like this:
#+BEGIN_SRC haskell
-- In this case we place Bool
in place of a
everywhere
instance Eq Bool where
(==) b1 b2 = case (b1, b2) of
(True, True) -> True
(False, False) -> True
_ -> False
#+END_SRC
Now we can implement polymorphic functions that will work on a subset of all types - all types that fill the constraint - have instances of a type class.
#+BEGIN_SRC haskell (/=) :: Eq a => a -> a -> Bool (/=) x y = not (x == y) #+END_SRC
class instances should be defined in the same place as the type class definition or at the same place as the type definitions. Failing to do that may cause [[https://wiki.haskell.org/Orphan_instance][Orphan Instances]].
| Abstraction | definition | different substitutions | comments |
|-------------------------+-------------------------------------+-------------------------------------------------------------+---------------------------------------------------------------------------------|
| No polymorphism | func1 :: Int -> Int -> Int | none | we know exactly which types are used and can do all kinds of operations on them |
| Parametric polymorphism | func2 :: a -> a -> a | a can be any type | We don't know which type a is and can't do any type related operations on it |
| Type classes (ad-hoc) | func3 :: Ord a => a -> a -> a | a can be any type that can be ordered (Bool, Int, String) | anything to the left of => is a constraint on the type |
*** More Material
*** Exercise
Note: We can create instances for higher kinded types (for example: Type -> Type). We will see some of those next.
** Monoids, Functors, Applicative, Monads and More
*** Overview
Key idea:
These are abstract algebraic structures
They define operations and laws on them such as identity and associativity.
Many patterns fit these structures, making them useful as abstractions!
Type classes you should care about (at the moment):
Semigroup
Monoid
Functor
Applicative
Monad
Foldable
Traversable
Read about them in the [[https://wiki.haskell.org/Typeclassopedia][typeclassopedia]] in this order.
After that: read [[http://dev.stephendiehl.com/hask/#monads][The monads section in wiwik]] to meet some useful monad instances.
*** Instances Make sure to meet:
And understand why and how they work! *** Exercises
#+BEGIN_SRC haskell data Either a b = Left a | Right b #+END_SRC
Simply put, a value of type Either a b can contain either a value of type a, or a value of type b.
Well can tell them apart from the contructor used.
#+BEGIN_SRC haskell Left True :: Either Bool b Right 'a' :: Either a Char #+END_SRC
Using this type, we can represent computations that may fail by using Either with one type to represent error values
and the other type to represent the values we want if the computation succeeds.
For example, let's say that we want to parse a String as a decimal digit to an Int. We have two possible failures:
We can represent this as a type
#+BEGIN_SRC haskell data ParseDigitError = EmptyString | StringIsTooLong | NotADigit Char deriving Show #+END_SRC
And our function can have the type
#+BEGIN_SRC haskell parseDigit :: String -> Either ParseDigitError Integer #+END_SRC
Now when we check our string we can return Left on error and Right on successful parsing.
#+BEGIN_SRC haskell parseDigit :: String -> Either ParseDigitError Integer parseDigit str = case str of -- empty string [] -> Left EmptyString -- more than one character _ : _ : _ -> Left StringIsTooLong [c] -> if elem c "0123456789" then Right (read [c]) else Left (NotADigit c) #+END_SRC
Either a is also an instance of Functor, Applicative, and Monad, so we have some combinators to work with
if we want to combine these kind of computations.
For example, we can use our function to parse an integer by trying to
parse each character (using traverse) and then use a function to sum them all together
by applying it to the Int value using fmap.
#+BEGIN_SRC haskell
parseInteger :: String -> Either ParseDigitError Integer
parseInteger str = do
if null str
then Left EmptyString
else
-- We use (:[]) first because each element of a String
is a Char
and our functions works on String
.
-- This also means that in this case only NotADigit error can be return, which is still fine.
let
digits = traverse (parseDigit . (:[])) str
in
fmap
( foldr (+) 0
. zipWith (\e n -> 10 ^ e * n) [0..]
. reverse
)
digits
#+END_SRC
Try it!
Note that since Either has kind Type -> Type -> Type and Functor, Applicative and Monad
expect something of kind Type -> Type, we can only create instances for Either a and not Either.
This means that when we use, for example, <*> which has the type
#+BEGIN_SRC haskell (<*>) :: Applicative f => f (a -> b) -> f a -> f b #+END_SRC
we replace f with Either a and not Either:
#+BEGIN_SRC haskell
-- We'll use e
for the left type of the either instead of a
here because a
is already taken
(<*>) :: Either e (a -> b) -> Either e a -> Either e b
#+END_SRC
This means that e must be the same. If you want, for example, to use two different error types,
two approaches you can use are:
*** Exceptions
It is a good idea to keep your code idiomatic and measure before you decide to use mutation and other fancier methods. You may not need it! *** Resources **** General
The choice of a data structure is determined by the properties of your data and the algorithms used.
Single-linked lists are a fairly ubiquious data structure in Haskell. Due to their simplicity and syntactic sugar, they're used all over the place - often when they're not a good choice.
Lists are good for:
Lists are not good if:
Monad transformers are a way to compose the capabilities of multiple type's monadic interface to one type.
[[http://slides.com/fp-ctd/lecture-7#][Haskell ITMO course at CTD - Lecture 7]]
[[https://two-wrongs.com/a-gentle-introduction-to-monad-transformers][A Gentle Introduction to Monad Transformers]]
[[https://www.schoolofhaskell.com/user/commercial/content/monad-transformers][School of Haskell - Monad Transformers]]
[[https://blog.jle.im/entry/mtl-is-not-a-monad-transformer-library.html][mtl is Not a Monad Transformer Library]] *** Exercises
To your [[#rpn-calculator][RPN Calculator]] REPL:
[[https://impurepics.com/posts/2019-08-01-haskell-extensions.html][Haskell Extensions in Pictures]]
[[https://limperg.de/ghc-extensions/][A Guide to GHC's Extensions]] ** Functional Patterns *** Effectful outer layer, Uneffectful core Code that does no effects is easier to test, debug and reason about.
Keeping most of our program's logic uneffectful makes it more flexible.
But programs still need to interact with the outside world.
For that, we can create an outer layer that is responsible for interacting with the user and dispatching the right logic functions.
Notice this pattern in these [[http://www.haskellforall.com/2015/10/basic-haskell-examples.html][Basic Haskell Examples]]. *** Compose Smaller Things to Bigger Things
You can find them when doing web development, streaming, IO, concurrency, parsing, error handling, testing, build systems and more.
Examples:
[[https://kseo.github.io/posts/2014-01-16-applicative-parsing.html][Applicative Parsing]]
[[https://hackage.haskell.org/package/lucid-2.9.10/docs/Lucid.html][Lucid - a DSL for writing HTML]]
[[https://www.oreilly.com/library/view/parallel-and-concurrent/9781449335939/ch10.html][Software Transactional Memory]] *** Parse, Don't Validate
[[https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/][Parse, don't validate]] *** More
[[https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/][The ReaderT Design Pattern]]
[[https://www.reddit.com/r/haskell/comments/5r271m/haskell_design_patterns/][Discussion on Reddit]]
[[https://gumroad.com/l/CLyzT][William Yao - Abstractions in Context (book)]]
[[https://algebradriven.design][Algebra-Driven Design (book)]] ** More *** Hands-on tutorials
[[https://marcosampellegrini.com/simple-haskell-book][The Simple Haskell Handbook - build a continuous integration server from scratch (book)]]
[[https://blog.jle.im/entry/streaming-huffman-compression-in-haskell-part-1-trees][Streaming Huffman Compression in Haskell]]
[[https://github.com/soupi/learn-haskell-blog-generator][Learn Haskell by building a blog generator]]
[[https://lokathor.gitbooks.io/using-haskell/content/][OpenGL Using Haskell]]
[[https://gilmi.me/blog/post/2016/10/14/lisp-to-js][Compiling Lisp to JavasScript from scratch in 350 LOC]]
[[https://wespiser.com/writings/wyas/home.html][Write you a Scheme, version 2]]
[[https://gilmi.me/blog/post/2020/12/05/scotty-bulletin-board][Building a bulletin board website using scotty and friends]] *** Project ideas
Morse code encoder/decoder
A file reader
Over the network rock-paper-scissors game
An RPN calculator
A markdown (subset) to HTML converter
A [[https://gemini.circumlunar.space][gemini]] server
Cookie clicker
A chat server and client
A picture album website
A pastebin clone
A tetris game
A discord bot *** Some Advanced Topics These may not be as useful for your everyday programming tasks, but it's nice to know they exist when you need them
[[https://en.wikibooks.org/wiki/Haskell/FFI][Foreign Function Interface]]
[[https://chrisdone.com/posts/data-typeable][Generic Programming]]
[[https://markkarpov.com/tutorial/th.html][Meta Programming with Template Haskell]]
[[https://lexi-lambda.github.io/blog/2021/03/25/an-introduction-to-typeclass-metaprogramming][An introduction to typeclass metaprogramming]]
[[https://diogocastro.com/blog/2018/10/17/haskells-kind-system-a-primer/][Haskell's kind system - a primer]] and [[https://www.parsonsmatt.org/2017/04/26/basic_type_level_programming_in_haskell.html][Basic Type Level Programming]]
[[https://blog.sumtypeofway.com/an-introduction-to-recursion-schemes/][Recursion Schemes]]
[[https://skillsmatter.com/skillscasts/4251-lenses-compositional-data-access-and-manipulation][Lenses]] *** References
[[https://haskell.fpcomplete.com/tutorial/operators][Operators Glossary]]
[[http://dev.stephendiehl.com/hask/][What I Wish I Knew Learning Haskell]] *** News Aggregators
[[https://haskellweekly.news/][Haskell Weekly News]]
[[https://haskell.pl-a.net/][Haskell Planetarium]] *** Simple Example Programs
[[https://anardil.net/tag/coreutils.html][Unix core utilities in Haskell]]
[[https://gist.github.com/soupi/199a16be6e2071c3b724][Simple File Reader]]
[[https://gitlab.com/gilmi/sdl2-snake][Snake Game]]
[[https://gitlab.com/gilmi/sod-cmus][Simplified Web Interface to cmus]]
[[https://gitlab.com/gilmi/imgs][Image Server]]
[[https://gitlab.com/gilmi/sharelinks][Minimalistic website for link sharing]]
[[https://github.com/jackoe/discourse-tui][A terminal UI for discourse]]
[[https://github.com/alpacaaa/quad-ci][Continuous Integration system]] *** A Few Cool Open-Source Applications Here are a few cool open source applications written in Haskell that might accept contributions if you're interested.
[[https://github.com/aurapm/aura/][Aura]] - A package manager for Arch Linux and its AUR.
[[https://gitlab.com/gilmi/bulletin-app][bulletin-app]] - A bulletin board website app built with haskell and scotty.
[[https://github.com/google/codeworld][CodeWorld]] - CodeWorld is an educational environment using Haskell.
[[https://lettier.github.io/gifcurry/][gifcurry]] - Your open source video to GIF maker built with Haskell.
[[https://giml-lang.org][Giml]] - A functional programming language built live on stream.
[[https://github.com/therewillbecode/haskell-poker][Haskell-Poker]] - A poker site built with Haskell.
[[http://hledger.org/][hledger]] - Friendly, robust, plain text accounting.
[[https://owickstrom.github.io/komposition][Komposition]] - The video editor built for screencasters.
[[https://github.com/matterhorn-chat/matterhorn][Matterhorn]] - A terminal client for the Mattermost chat system.
[[https://github.com/lettier/movie-monad][Movie-Monad]] - A free and simple to use video player made with Haskell.
[[https://neuron.zettel.page/][neuron]] - A future-proof command-line app for managing your plain-text Zettelkasten notes.
[[https://gilmi.me/nyx][nyx-game]] - A short bullet-hell game made with Haskell.
[[https://github.com/jaspervdj/patat][patat]] - Terminal-based presentations using Pandoc.
[[https://github.com/begriffs/postgrest][postgrest]] - REST API for any Postgres database.
[[https://github.com/purescript/purescript][PureScript]] - A strongly-typed language that compiles to Javascript.
[[https://github.com/agentm/project-m36][Project:m36]] - A relational algebra engine as inspired by the writings of Chris Date.
[[https://taskell.app/][Taskell]] - Command-line Kanban board/task management.
[[https://github.com/cdepillabout/termonad][termonad]] - A terminal emulator configurable in Haskell.
[[https://github.com/tidalcycles/Tidal][Tidal]] - Language for live coding of pattern.