Good Haskell Libraries

25 points by abhin4v a day ago on lobsters | 10 comments

/wiki/haskell/libraries

Last updated: April 9, 2025

Good Haskell Libraries

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.

Alternative Preludes

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

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

Developer Ergonomics

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

Data Structures

  • 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 Maps together, checking for coincidnet FRP Events, and more.

  • witherable generalises mapMaybe from lists to a large class of data structues.

Enumerating Complex Structures

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

Error Handling

  • 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).

Text Manipulation

Regex

Haskell programmers tend not to use regexen, because parser combinators are often easier to read and harder to get wrong. As kqr puts it, “In other languages, it would be considered overkill to write a full parser when a simple regex can do the same thing. In Haskell, writing a parser is no big deal. We just do it and move on with our lives.”

  • regex-tdfa is my default choice. It’s a pure Haskell implementation (no FFI), and I’ve never had performance trouble with it. The flexibility offered by the regex-base typeclasses is a little intimidating at first, but crib from the examples and you’ll be fine.

Stateful Stuff

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

Graph Stuff

  • 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

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