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:
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.
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
.