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 non-boot dependencies of its own,
and provides quite a few things to make the boring stuff a little nicer.
Some examples:
Map
and
HashMap
;viaNonEmpty :: (NonEmpty a -> b) -> [a] -> Maybe b
;(<<$>>) = fmap . fmap
; andusingReaderT = 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.
placeholder
is a well-designed TODO library, which provides “placeholders” that
raise warnings for the programmer to come back and fill out. Unlike the
todo
provided by relude
, it also provides a
pattern synonym which can be handy.
safe-wild-cards
uses Template Haskell to provide an alternative to
RecordWildCards
-style pattern matches, that generate
warnings if any bindings are unused.
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.
finitary
is “class Enum
done right”. It provides a
Finitary a
typeclass that exposes the cardinality of
a
at the type level, witnesses the bijection between
a
and Finite (Cardinality a)
, and provides
safe next
/previous
/enumeration functions for a
type a
.
If finitary
’s GPL-3.0-or-later
licence
is unacceptable, its bounds are not up-to-date, or you need to enumerate
a data type with infinitely many members, the universe
can be a handy lightweight alternative.
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.
safe-coloured-text
is a cross-platform text-colouring library.
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
.