Last updated: August 30, 2023

The Haskell library ecosystem these days is incredibly rich, and I think many good libraries are overlooked. This is my list of favourites, either because they’re my favourite solution to a class of problem (e.g., streaming, effect systems), or because they are elegant and overlooked.

If writing a library, I never use an alternative prelude. Alternative preludes reorganise instead of providing new features, almost by definition, so the risk/reward payoff for the additional dependency just isn’t there. Every dependency your library takes on is forced on all downstream users, and is a potential bottleneck if anything happens to its maintainer.

For applications, I still generally prefer

`base`

. Of the alternative preludes, I think that`relude`

is the best. It pulls in no additional dependencies of its own, and provides quite a few things to make the boring stuff a little nicer. Some examples:- Imports of common type names like
`Map`

and`HashMap`

; - Partial functions are hidden;
- Helpers for non-empty data types, like
`viaNonEmpty :: (NonEmpty a -> b) -> [a] -> Maybe b`

; `(<<$>>) = fmap . fmap`

; and`usingReaderT = flip runReaderT`

I disagree with the recommendation to fiddle with mixins; I’d rather set

`default-extensions: NoImplicitPrelude`

and explicitly`import Relude`

in each file to keep things clear.- Imports of common type names like

`semialign`

provides a function`align :: Semialign f => f a -> f b -> f (These a b)`

, where`These a b`

is an “`a`

or`b`

or both” data type. This is useful in a lot of places: zipping lists but keeping the tail of the longer list, zipping`Map`

s together, checking for coincidnet FRP`Event`

s, and more.`witherable`

generalises`mapMaybe`

from lists to a large class of data structues.

`hoist-error`

is a small combinator library for lifting the “failure case” of`Maybe`

and`Either`

values into the “error part” of a`MonadError e m`

. This tends to make the “happy path” through a function much clearer, cut down on “boring`case`

matching”, and reduce`>>= either (throwError . MyError) pure`

noise.There are a few libraries that provide a

`Validation`

type that’s isomorphic to`Either`

, but gives up the`Monad`

instance to provide an`Applicative`

instance that “accumulates the errors”. I recommend`validation-selective`

for its excellent documentation and a good`Semigroup`

instance.`instance (Semigroup e, Semigroup a) => Semigroup (Validation e a)`

will`(<>)`

failures with failures and successes with successes.If you want to accumulate errors but also need a

`Monad`

instance, consider`monad-chronicle`

. However, some things to be cautious about:When used as an applicative,

`ChronicleT`

accumulates fewer errors than a`Validation`

applicative, because`ChronicleT`

’s`Applicative`

instance must agree with its`Monad`

instance.`ChronicleT`

shares many of the same laziness concerns as naive`WriterT`

, and can build up large thunks in its first type parameter (the “error” one).

`formatting`

provides an expressive DSL for formatting text. Unlike`Text.Printf`

,`formatting`

is type-safe.

`ref-tf`

lets you write functions over any monad which provides mutable references, which means you can provide an interface that works in`IO`

as well as`ST s`

.`StateVar`

provides a typeclass for “reference-like” values, backed by arbitrary I/O. This is occasionally extremely useful for providing interfaces to fundamentally stateful things, like the OpenGL 1.1 API.

`algebraic-graphs`

is an extremely elegant graph library built atop a Functional Pearl.`search-algorithms`

is a delightful little package which provides classic algorithms like BFS, DFS, Dijkstra’s, and A*. It doesn’t care what data structure you use for your graph, as it takes the next state relation and edge cost information as function parameters.

`streaming`

is my favourite streaming library. You might imagine an effectful stream of`a`

over a`Monad m`

as:`data Stream a m r = Step !a (Stream a m r) | Effect (m (Stream a m r)) | Return r`

`streaming`

generalises the`a`

to an arbitrary functor, and actually uses the following two main types:`data Of a b = !a :> b deriving Functor data Stream f m r = Step !(f (Stream f m r)) | Effect (m (Stream f m r)) | Return r`

Most of the time, you work with

`Stream (Of a) m r`

, but the functor parameter enables things that seem much harder to write in`conduit`

or`pipes`

. If I’m connecting a source directly to a sink, I usually stick with whatever streaming library underlies other libraries I’m using, which often means using`conduit`

.