Pipes define a free monad

Posted on April 26, 2018

The core type of the pipes library is a free monad. Indeed, Pipes.Proxy is equivalent to Free (ProxyF _ _ _ _ _), where

data Free f a
  = Free (f (Free f a))
  | Pure a

data ProxyF a' a b' b m x
  = Request a' (a  -> x)
  | Respond b  (b' -> x)
  | M          (m     x)

That’s nothing ground-breaking if you’re familiar with free monads. In fact, it used to be the literal definition of pipes, using free before I even knew Haskell was a thing.

Nevertheless, my previous understanding of pipes was mostly based on the interface (which is very nice by the way; the diagrams in Pipes.Core are a pleasure to contemplate), while I needed all my focus to follow the implementation. Hence, in spite of now extensive experience with free monads, it took one more wander in front of the source code of pipes for me to have that epiphany.

A much deeper understanding of the whole business followed immediately. With free monads as a common starting point1, it became much easier to compare pipes, conduit, and quiver: basically, they’re all using free monads for differing I/O-like functors2.

Now it may be just another occasionally useful fun fact, but that one somehow felt like a personal achievement. That realization gave me a strong impression of the power of abstraction.


  1. Free encapsulates a form of recursion (it’s a sibling of Fix).

  2. quiver with its polymorphic continuations is a bit of an outlier, but thinking in terms of free monads is at least good enough as a first approximation.