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. class of problem.
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.
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.
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
.