@hackage free-listt0.1.0.1

Lawful list and set monad transformers based on free monads

Free ListT

This package provides a lawful ListT implementation based on free monads. It also provides an Applicative list transformer, which is a lawful Applicative, and isomorphic to the old ListT.

Background

The old ListT transformer from transformers < 0.6 was unlawful: For a noncommutative monad, ListT m was not a monad. It was implemented simply as the composition of list and m:

newtype ListT m a = ListT (m [a])

As such, it is automatically a lawful Functor and Applicative in a canonical way. But famously, a composition of two monads is not always a monad, and in this case, it in fact isn't.

Previous approaches

Streaming

There is one popular approach to ListT, representing the transformer as a stream:

newtype ListT m a = ListT (m (Maybe (a, ListT m a)))

This approach is implemented in https://hackage.haskell.org/package/list-t and https://hackage.haskell.org/package/list-transformer, and it is a great choice in many cases.

Basically, to go through the list, one has to perform one effect in m at each step, and one discovers whether the list now ends or produces a further element. But this also means that the list structure enforces all earlier m effects before a later element can be accessed.

Church-encoding

Another possibility is Church-encoding the list, which is implemented in logict.

Free approach

A simple algebraic approach that does not enforce the linear structure of streaming ListT is a free monad:

newtype ListT m a = ListT (FreeT [] m a)

This gives a branching rose tree of computations, where at every step in m, arbitrarily many branches can arise.

Unfolding the definition of FreeT as an algebraic datatype would result in something like:

data ListT m a = OneLayer (m a) | TwoLayers (m [m a]) | ThreeLayers (m [m [m a]]) | ...

Applicative transformer

As mentioned already, the old ListT is a valid Applicative transformer, which means that ListT m is a lawful Applicative as long as m is. This is useful and sufficient in many situations, which is why it is reinstated here.

Also, values of type m [a] occur very often in the wild (for example when using mapM), so it makes sense to give them a proper type that captures their properties.

To give some more examples, all lawful ListT implementations have some kind of "running" function that maps it to m [a]. The free ListT is no exception here. In a sense, one can "flatten" or "concatenate" all free list layers into a single one. While this is a natural transformation, this is of course not a monad morphism.