This is a quick "zero to hero" guide of various Haskell and FP-related topics. The intended audience (you) is self-directed people that want to quickly become a world-class Haskell programmer. Actually many of the topics are not specific to Haskell, so it can probably also help you become a world-class programmer in a similar language too.
The format is intentionally terse - most of the content does not even consist of full sentences. This is not just because I am lazy - rather, it's 2020 and we have the internet, so there are plenty of very good resources out there for going in deep into any particular topic, at least in computer science. However if you're completely fresh starting out in some topic, it's really not obvious what you should look at first. Often you type something into Your Favourite Search Engine, find some cool looking stuff, then have to spend a few weeks doing depth-first-search or breadth-first-search over all the jargon you come across, that is necessary background to understand the world-class texts.
These documents are intended to make this much quicker, by telling you a good order to explore topics in, starting from the basic ones. It is by no means complete, nor objective - it presents my/our views of a good "trunk" of progress, and omits side branches that you are supposed to figure out for yourself. If you get stuck or can't find inspiration on how to explore what's out there, simply do a web or video search for "$phrase tutorial".
Even with the theory topics, you should write some code (or if unavailable, do some practical exercises on paper) that implements whatever idea it's talking about. I and many others find this to be much more effective at really understanding something, than merely reading about it.
https://byorgey.wordpress.com/2009/01/12/abstraction-intuition-and-the-monad-tutorial-fallacy/
Struggle is somewhat necessary.
- Basic syntax and functional style
- Basic type system, data types and polymorphism
- Functor, Applicative, Monad - in pictures
- Monad combinators (forM, when, etc)
- Foldable, Traversable
- Monoid, Semigroup
It's 2020, just use cabal "new-style" commands. We don't need stack any more.
grep/rg, these are more flexible than any IDE, learning to use them will save you tons of time
Hoogle, useful for when you remember a type signature but forgot the name
I don't need/use a fancy IDE, just a simple one (Geany) that lets me (1) "jump to definition" (ctrl-click) and (2) code folding.
Type Holes are very useful for debugging or building up a new complex thing from scratch.
Watch out for accidental recursion. Haskell is slightly annoying in that recursion is implicit so if you accidentally write something like:
let thing1 = ... innocent ... let thing1 = ... thing1 ...
this will cause a runtime infinite loop that sometimes is confusing to debug.
(In OCaml and others, the last thing1
refers to the first thing1
.)
Get familiar with this, it is not time-efficient to skip this part.
Type signatures using partially-applied functions
- using functions as arguments
- returning functions
Currying, especially curried/uncurried functions given as input to combinators.
Get familiar with this, it is not time-efficient to skip this part.
How to mechanically - i.e. without really thinking about it - convert iterative algorithm to functional/recursive, e.g. using accumulators when needed. Try doing it manually and intuitively first, then read about the general technique which is actually very mechanical: defunctionalise the continuation (more detailed)
foldl/foldr, space leaks
Y fixed-point combinator and some common usages (e.g. memoisation). But don't spend too long on this, recursion schemes (much later) covers this more deeply.
Eq, Ord, Show, Read, Enum, Bounded
Generic
deriving
(cabal packages): bytestring, containers, unordered-containers
Get familiar with the standard monad transformers (ReaderT, WriterT, StateT) and MonadTrans/lift.
Slightly more advanced: MaybeT, ExceptT; ignore ContT for now
Bad design pattern: a monad transformer with more than copy of a standard transformer, e.g. "ReaderT env1 (ReaderT env2 IO)". Instead write your own application-specific monad, perhaps wrapping standard ones inside a newtype. Don't build a monad transformer stack too high, basically.
seq and $!, BangPatterns, strict field annotations
GHC RTS profiling, ghc-heap-view
strict data structures, problem with lazy instances of common data structures e.g. haskellari/strict#22
TL;DR: use strict fields for long-running ("state-like") state, lazy (default)
data types for "control-like" state. This is much more convenient / intuitive
than the standard advice to sprinkle seq
at random places in your code.
Understand that typeclass constraints are solved using information available on the caller's side, it helps to think of them as function inputs (which is actually what they are desugared into).
Get experience in debugging monad transformer errors, which situations are fixable or impossible to fix.
IO, ST monad, files, network, etc
STM, queues
Threads, Async, exceptions, blocking operations, interruptibility, masking Control.Exception.Safe, MonadUnliftIO and their caveats
Much "real-world" Haskell (e.g. today's production systems) only use techniques up to the end of the previous chapter then abuse the ReaderT design pattern which is just global variables implemented in Haskell - convenient but misses the point and power of doing stuff in Haskell in the first place.
It is possible to be very productive with this pattern, but IMO this is basically writing non-Haskell impure code in Haskell. To take advantage of Haskell's full power to express well-structured composable pure code, one should avoid this pattern and use the techniques below.
ScopedTypeVariables
Pattern matching to expose an existential type. Continuation Passing Style "withSomeX" design pattern to make this reusable.
Phantom types, Proxy vs TypeApplications, AllowAmbiguousTypes
https://wiki.haskell.org/GHC/Type_families
https://mmhaskell.com/blog/2019/2/4/why-haskell-v-type-families
Associated type synonym is similar to "associated types" in Rust/Scala
- Alternative
- Selective
- Bifunctor, Bitraversable
- Category
- Arrow (Arrow notation, combinators and transformers)
- Profunctor
Unboxed types
FFI
The below is not really necessary for most programming tasks, but it's useful info for those that want some deeper level of understanding, or that want to follow the latest state-of-the-art in PL research.
Core - Video lecture
STG
https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/ First 6 chapters for now
https://www.olivierverdier.com/posts/2014/12/31/reader-writer-monad-comonad/
http://slides.com/fp-ctd/lecture-13#/44
http://hackage.haskell.org/package/comonad vs http://hackage.haskell.org/package/transformers
- Reader vs Traced
- Writer vs Env
- State vs Store
https://tac-tics.net/blog/data-vs-codata
https://blog.sumtypeofway.com/posts/introduction-to-recursion-schemes.html and https://blog.sumtypeofway.com/posts/recursion-schemes-part-6.html
- Pipes/Coroutines/ConduitT/Stream - Pipes Tutorial, then try to understand Conduit space leaks
- ContT (ugh, but necessary to have a basic understanding of)
- Comonad, etc.
Basic higher-kinded programming (heterogeneous lists and trees)
Singletons https://blog.jle.im/entry/introduction-to-singletons-1.html
Review singletons, including defunctionalisation (pointfree.io is useful)
Singletons P-* and S-* typeclasses, and using them
Recall: CPS for exposing existentials / passing proofs around, e.g. withSomeSing
Writing proofs as constraints https://www.youtube.com/watch?v=wNa3MMbhwS4
https://www.reddit.com/r/haskell/comments/g3lnk4/questions_on_actually_doing_dependentlytyped/
constraints package for avoiding CPS in more complex scenarios https://gist.github.com/Lysxia/2e110d801a1d1cccdf2863a7431709de
Free monad, freer monad
Polysemy & the general idea of "action/effect interpreters"
Eff & delimited continuations