| Safe Haskell | Safe-Inferred |
|---|
Documentation.Layers.Overview
Contents
Description
Warning: This module exports no types, functions, classes or instances. It exists solely for the Haddock documentation it produces. You should not ever need to import it.
This document discusses extensively the motivation behind the layers package
and explains the design decisions taken in its construction.
Monads and monad transformers
Monads are used everywhere in Haskell. Monads in general represent
computations, and specific monads represent computations capable of specific
features. For example, there is a monad that encapsulates stateful
computations, a monad that encapsulates computations which may fail (and
recover from failure) and a monad that encapsulates computations which perform
I/O. When writing applications in Haskell, we often wish to use a combination
of these features all at once. The most common way to do this is to use monad
transformers. Monad transformers are monad "constructors" that take an
existing monad and return a new monad that has all the features of the
original monad in addition to any features that were added by the monad
transformer. Haskell applications often use a custom monad which consists of a
stack of (standard) monad transformers layered on top of some base monad. The
standard monad transformers are provided by the transformers package, which
at the time of writing has 676 reverse dependencies on Hackage.
For the "monad transformer stack" design pattern to be usable, quite a lot
of type class machinery is required. As it stands, some of this machinery
lives in the transformers package (MonadTrans,
MonadIO), while the rest is provided by the mtl
(monad transformer library) package (MonadReader, MonadState, etc.: the
documentation of this package refers to these sorts of type classes as "monad
interfaces"). However, the type class machinery provided by these packages is
limited in many important respects and it doesn't allow us to do many of the
things we might expect to be able to do.
Limitations of the existing machinery
lift is not powerful enough
One of the most glaring weaknesses of the existing type class machinery is
that the lift operation provided by
MonadTrans is not very powerful. It's only capable of
lifting very simple types of operations up from the inner monad. Consider the
local operation from the
MonadReader interface:
local :: MonadReader r m => (r -> r) -> m a -> m a
It should be possible to automatically lift local through every conceivable
monad transformer, even ContT. However, with lift
alone, it is impossible. The MonadTrans class clearly needs at least one
more operation in addition to lift to allow this. And while it should be
possible to automatically lift local through every possible monad
transformer, there are some operations which can be lifted through most
monad transformers, but not all. These sorts of operations are often called
"control" operations. One example is
catch from the
MonadException interface:
catch :: (Exception e, MonadException m) => m a -> (e -> m a) -> m a
catch can be lifted through most monad transformers, but not ContT. Given
that there is a class of monad transformers through which control operations
can be lifted, one would expect there to be a type class (a subclass of
MonadTrans) for such monad transformers, but none is provided by the
transformers library. There are other packages which define such classes,
some of which are discussed below, but none of them are standard.
Non-modularity, code duplication
Imagine you are designing a monad transformer Foo. A common design pattern
would be to define a type constructor FooT :: (* -> *) -> * -> * in the
module Control.Monad.Trans.Foo, and a function runFooT :: FooT m a -> m a.
Let's say that your monad transformer adds a single operation, getFoo, to
the transformed monad:
getFoo :: Monad m => FooT m Foo
getFoo, defined in this way, will only work when FooT is at the top of the
transformer stack, but we would like it would work on any monad built from a
transformer stack containing FooT anywhere in the stack. The common design
pattern achieves this by defining in the Control.Monad.Foo.Class module a
MonadFoo class like the following:
class Monad m => MonadFoo m where
getFoo :: m Foo
instance Monad m => MonadFoo (FooT m) where
getFoo = FooT $ return Foo
So now we have a class that represents every monad that supports the getFoo
operation, and we have an instance for FooT. But where are the instances for
other monads which support getFoo (i.e., monads built on top of FooT)? You
might expect there to be an instance like:
instance (MonadTrans t, Monad (t m), MonadFoo m) => MonadFoo (t m) where
getFoo = lift getFoo
But this is not what is generally done. There are two reasons for this: one is
that lift is not powerful enough to lift all types of operations, so for
many interfaces it isn't possible to define a universal pass-through instance
that can lift all the required operations through an arbitrary monad
transformer (although our MonadFoo interface happens to only contain
getFoo which is simple enough for lift). The second reason is that such
instances require the OverlappingInstances extension, which is considered
far too controversial to be made a requirement for such a core part of the
Haskell ecosystem (the monad transfomer library).
So instead what monad transformer authors generally do is create pass-through
instances for each of the monad transformers in the transformers library:
instance MonadFoo m => MonadFoo (ReaderT r m) where
getFoo = lift getFoo
instance MonadFoo m => MonadFoo (StateT s m) where
getFoo = lift getFoo
...and so on. This is a lot of code duplication. If you have n monad
transformers and m monad interfaces, you need to define O(n * m) instances
to ensure that each monad interface can pass through each transformer. This
means n identical instances for each monad interface (or at least they would
be identical if there was a sufficiently powerful complement to lift). But
the code duplication isn't the worst part: the worst part is how this destroys
modularity. It means that every third-party monad interface can only be
lifted through the standard monad transformers. If you want to lift a third
party monad interface through a third party monad transformer, your only hope
is to define an orphan instance, which means your application will break if
any of the libraries you depend on ever define this instance themselves.
Redundant type classes
The transformers package includes the MonadIO
interface. It provides a single operation, liftIO.
This is unsatisfactory in a lot of ways. Firstly, liftIO suffers from the
same lack of power as lift: only very simple types
of IO operations can be lifted with liftIO. Secondly: while obviously it's
particularly useful to be able to lift operations from IO through an
arbitrary stack of monad transformers, and it makes sense to have a type class
for the class of monads capable of performing I/O (including IO itself),
there's no theoretical reason why IO should be a special case like this. It
should be equally valid to have a MonadST class that represents all monad
stacks that have ST as their base monad, but obviously it
would be ridiculous to have to define a new type class for every possible base
monad. Instead all that's needed is a class like the following:
class MonadBase b m | m -> b where
liftBase :: b a -> m a
Indeed the transformers-base package does define such a class. However, that
means there are two incompatible ways of expressing the same constraint:
MonadIO m and MonadBase IO m. It would be better to do away with MonadIO
completely, or at least redefine it as a synonym for MonadBase IO.
However, even this is unsatisfactory, at least on its own. If
lift is for lifting operations from the monad just
beneath the top of the stack, and liftBase is for lifting operations from
the monad at the very bottom of the stack, what operation do we have for
lifting operations from every monad in between? What we really want is a fully
polymorphic lift that can lift operations from any monad anywhere in the
stack. Such an operation would unify lift, liftIO, liftBase and friends
into a single operation that could do all of these things and more.
Other (partial) solutions
hoist
The pipes package contains a module Control.MFunctor which exports the
following class:
class MFunctor t where
hoist :: Monad m => (forall b. m b -> n b) -> t m a -> t n a
The documentation describes MFunctor as "a functor in the category of
monads". Its hoist operation lifts a homomorphism between monads to a
homomorphism between inner monads wrapped by the same transformer. The
combination of hoist and lift is powerful enough to define a universal
pass-through instance for the MonadReader
interface discussed above:
instance (MonadTrans t, MFunctor t, Monad (t m), MonadReader r m) =>
MonadReader r (t m)
where
ask = lift ask
local f = hoist (local f)
The crucial question then is: is hoist the missing operation from
MonadTrans? Can every type that is currently an
instance of MonadTrans be made an instance of MFunctor too? The answer,
unfortunately, is no. Once again, ContT proves too
stubborn, and won't permit an instance of MFunctor. However, the idea behind
MFunctor/hoist is "close", and it certainly seems to be on the right
track.
mmtl
There is an alternative to mtl on Hackage called mmtl: the modular monad
transformer library. It's an old package, and it no longer builds with recent
versions of GHC, but it's worth studying. Its version of the MonadTrans
class contains an extra operation tmap:
class MonadTrans t where
lift :: Monad m => m a -> t m a
tmap :: (Monad m, Monad n)
=> (forall b. m b -> n b)
-> (forall b. n b -> m b)
-> t m a
-> t n a
This tmap operation is very similar to the hoist operation in the pipes
package. It lifts an isomorphism between monads to a homomorphism between
inner monads wrapped by the same transformer. (In addition to the morphism
between monads that hoist takes, tmap also takes that morphism's inverse.
In other words, tmap is less powerful than hoist, because it can only lift
morphisms which have an inverse, while hoist can lift any moprhism.) This
type of operation is sometimes called an invariant functor: if hoist is
"a functor in the category of monads", then tmap is "an invariant functor
in the category of monads". Invariant functors from the category of Hask
can be represented by the following type class:
class Invariant f where
invmap :: (a -> b) -> (b -> a) -> f a -> f b
This class is defined in the invariant package. What's interesting is that
the documentation of Invariant states that it's possible to define an
instance of Invariant (i.e., an invariant functor from the category of
Hask) for every * -> * type parametric in the argument. If we translate
this to the category of monads, that means that it's possible to define an
invariant functor (i.e., tmap) for every (* -> *) -> * -> * type
parametric in the argument: this is exactly what monad transformers are. So
unlike hoist, it's possible to define tmap for every possible monad
transfomer, even ContT. The crucial question for us
now is: is tmap powerful enough to lift
local? Can we define a universal pass-through
instance of MonadReader? The answer is yes!
instance (MonadTrans t, Monad (t m), MonadReader r m) =>
MonadReader r (t m)
where
ask = lift ask
local f m = lift ask >>= \r -> tmap (local f) (local (const r)) m
As with hoist above, we pass local f to tmap to lift the morphism into
the transformed monad. The tricky part though is that tmap also requires us
to give it the inverse of local f. How can we get the inverse of local f?
It could be local g if we knew a function g which was the inverse of f
(such that g . f = id), but we don't. However, given that we know that
local f just applies a transfomation f to the environment r of a reader
monad, we can cheat by using const r (where r is value in the environment
prior to running local, which we obtain by asking the inner monad using
lift ask) as the function we pass to local. This leaves us with
tmap (local f) (local (const r)), which does the job. (Although const r is
not the inverse of f, we know that r is the value applied to f by
local, which means that the invariant local (const r) . local f = id
holds true.)
So what is the problem with mmtl then? Well, as stated above, it has
bitrotted to the point where it no longer builds with recent versions of GHC.
But it also defines its own versions of all the monad transformers from
transformers. transformers has 676 reverse dependencies on Hackage, and
the types that are defined in that package are used everywhere in the
Haskell ecosystem. No matter how much of an improvement a package is on
transformers, it will never take off unless it remains compatible with it.
The proof of this is that mmtl has only three reverse dependencies on
Hackage.
Also, remember we mentioned above that while local should be able to be
automatically lifted through every monad transformer, there should also be a
subclass of monad transformers which can lift control operations like
catch? mmtl does not do anything to
provide this class (although we are free to build it on top of mmtl
ourselves).
MonadCatchIO
One of the first packages that attempted to deal with the "catch" problem to
gain widespread adoption on Hackage was MonadCatchIO-{mtl,transformers}. It
defines a type class MonadCatchIO specifically for the purpose of lifting
the catch operation through monad transformer stacks:
class MonadIO m => MonadCatchIO m where
catch :: Exception e => m a -> (e -> m a) -> m a
block :: m a -> m a
unblock :: m a -> m a
(It also includes the (deprecated) operations block and unblock for
dealing with asynchronous exceptions, but we'll ignore asynchronous exceptions
for the moment and focus on catch.) While MonadCatchIO succeeds at being
the class of monads into which catch can be lifted, that is all it is. If we
want to lift from IO (or indeed any monad) any other control operation, we
need to define a separate type class just for lifting that operation. That is
unacceptable. What we're really looking for is the class of monad transformers
through which control operations can be lifted, but MonadCatchIO doesn't
get us any closer to that.
Another problem with MonadCatchIO is with the bracket operation it
provides, defined in terms of catch. Consider the following example:
foo = runMaybeT $ bracket
(lift (putStrLn "init"))
(\() -> lift (putStrLn "close"))
(\() -> lift (putStrLn "running") >> mzero)
Running foo in GHCi, you might expect it to produce the following output:
>>>fooinit running close Nothing
However, instead the following is produced:
>>>fooinit running Nothing
The finaliser was never run. What this shows is that the naive implementation
of the bracket family of operations is deficient when lifted through a monad
transformer stack containing one or more short-circuiting monad transformers.
(The short-circuiting monad transformers provided by the transformers
package are ErrorT, ListT and MaybeT). This hereafter is referred to as
"the bracket problem".
monad-control
There is another family of packages in this solution space; monad-peel and
monad-control. (monad-control started as a faster replacement for
monad-peel, but its design has diverged from the original monad-peel
design over time.) They both provide a subclass of
MonadTrans called MonadTransControl
(monad-peel calls it MonadTransPeel), and this is the type class that
we've been looking for! It represents the subclass of monad transformers
through which control operations can be automatically lifted. The latest
version of monad-control implements MonadTransControl as follows:
class MonadTrans t => MonadTransControl t where
data StT t :: * -> *
restoreT :: Monad m => m (StT t a) -> t m a
liftWith :: Monad m
=> ((forall n b. Monad n => t n b -> n (StT t b)) -> m a)
-> t m a
Where to begin? The first thing to note is the associated data type StT.
For a given monad transformer t, StT t represents the "state" added that
t adds to the monad it wraps. For example, StT MaybeT a =~ Maybe a and
StT (ErrorT e) a =~ Either e a. A good rule of thumb for figuring out what
StT t is for a given transformer t is to look at the definition of t:
data {t} m a = {t} { run{t} :: m (Foo a) }
If t is defined as above, then StT t a =~ Foo a. In general, StT t will
be the type of everything "inside" the m in the definition of the
transformer. The trick is to extract the component of "monadic state" added
by t which is independent of the underlying monad m.
The next thing to note is restoreT, which is pretty simple. It just takes
the "monadic state of t" (StT t a) wrapped in m, and "restores" it
to a value of t m a. For most transformers, restoreT is trivial: e.g., for
MaybeT, restoreT =~ MaybeT, modulo the unwrapping of the StT value.
It's the liftWith operation where the real "magic" of the monad-control
package happens. It's a CPS-style operation which takes a function which takes
a function, to which liftWith passes an operation (usually called run) of
type forall n b. Monad n => t n b -> n (StT t b). run is basically the
inverse of lift: if lift wraps a transformer t around a monadic action,
run peels a transformer off a monadic action, storing its monadic state in
an StT t value inside m. The type variable n in the signature of run
is usually instantiated to m, but it's type is polymorphic over all monads
to statically ensure that there are no remaining side effects in m in the
resulting action. Using something like MonadTransControl, it's possible to
define a universal pass-through instance for the
MonadException interface as follows:
instance (MonadTransControl t, Monad (t m), MonadException m) =>
MonadException (t m)
where
throw = lift . throw
catch m h = do
st <- liftWith (\run -> catch (run m) (run . h))
restoreT (return st)
The monad-control package also includes a class MonadBaseControl, which is
a subclass of the MonadBase class from transformers-base (described
above):
class MonadBase i m => MonadBaseControl i m | m -> i where
data StM m :: * -> *
restoreM :: StM m a -> m a
liftBaseWith :: ((forall b. m b -> i (StM m b)) -> i a) -> m a
(MonadBaseControl is to MonadBase as MonadTransControl is to
MonadTrans. While lift lifts operations from the monad just beneath the
top of the transformer stack, liftBase lifts operations from the monad at
the very bottom of the stack. lift and liftBase can only lift very basic
types of operations, liftWith and liftBaseWith can lift much more
complicated types of operations. MonadBaseControl uses the same technique to
achieve this with liftBaseWith as MonadTransControl uses with liftWith.)
MonadBaseControl is used extensively in the lifted-base package, whose
author is also the author of monad-control. It re-exports many of the IO
operations in the base package, but lifted to work with any monad built from
a transformer stack with IO as its base monad. This includes a version of
bracket, defined as follows:
bracket :: MonadBaseControl IO m => m a -> (a -> m b) -> (a -> m c) -> m c
bracket before after thing = do
st <- liftBaseWith $ \run -> E.bracket
(run before)
(\st -> run $ restoreM st >>= after)
(\st -> run $ restoreM st >>= thing)
restoreM st
Let's see if this version of bracket can handle the bracket problem
correctly:
foo = runMaybeT $ bracket
(lift (putStrLn "init"))
(\() -> lift (putStrLn "close"))
(\() -> lift (putStrLn "running") >> mzero)
>>>fooinit running close Nothing
Success! But this success comes at a cost. Consider a more complicated example:
bar :: IO (Maybe (), Int)
bar = flip runStateT 0 $ runMaybeT $ bracket
(do
liftBase $ putStrLn "init"
modify (+1))
(\() -> do
modify (+16)
liftBase $ putStrLn "close"
modify (+32)
mzero
modify (+64))
(\() -> do
modify (+2)
liftBase $ putStrLn "body"
modify (+4)
mzero
modify (+8))
(Adding a different power of two with each modify operation is equivalent to
setting a different bit of the state. By looking at the final value of the
state we can see exactly which modify operations were run.) We would expect
the following to output:
>>>barinit body close (Nothing,55)
Instead, however, we get:
>>>barinit body close (Nothing,7)
While monad-control's bracket operation manages to run the finaliser even
in the presence of a short-circuiting monad transformer, the reason it is able
to do this so is because it liberally discards the monadic state of the outer
transformers. The finaliser is run with the monadic state that existed after
the initialiser finished, not the monadic state after the "body" (which is
what one would expect). Additionally, the monadic state after the finaliser is
discarded, so the final monadic state after bracket completes is the monadic
state that existed after the "body" exited, not after the finaliser exited.
This makes sense if you look at the definition of bracket above: bracket
discards any potentially "zero" (i.e., short-circuiting) monadic state that
could prevent the finaliser from being run. While this solution "works", the
above example shows that it can lead to incorrect results as shown above. We
do not consider that monad-control solves the bracket problem
satisfactorily.
layers' solution
Monad layers
The most fundamental type class in layers, and its replacement for
MonadTrans, is MonadLayer.
Unlike MonadTrans, which is instantiated with types of kind
(* -> *) -> * -> * (i.e., monad constructors), MonadLayer is instantiated
on normal monads (of kind * -> *). Simply put, a monad m can be an
instance of MonadLayer if it is built ("layered") on top of some inner
monad . (Inner mInner m is an associated type that
must be provided by instances MonadLayer.) It looks like the following:
class (Monad m, Monad (Inner m)) => MonadLayer m where
type Inner m :: * -> *
layer :: Inner m a -> m a
(layer is the MonadLayer equivalent to
lift.)
Note that unlike monad transformers, monad layers are not necessarily
parametric in their inner monad. This means that the class of layers, in
addition to including all valid monad transformers, also includes monads which
are implemented on top of some fixed inner monad. For example, it's common to
define an Application monad in terms of some transformer stack based on
IO, and then use liftIO to lift IO computations
into the Application monad. Application could look like the following:
newtype Application a = Application (StateT AppState IO a) deriving (Functor, Applicative, Monad, MonadState)
While it clearly isn't a monad transformer (it doesn't even have the right
kind), it's clearly in some sense a "layer" around IO, and we would like
to be able to lift computations from IO to the Application monad.
MonadLayer allows us to express this (without resorting to hacks like
MonadIO):
instance MonadLayer Application where
type Inner Application = IO
layer = Application . layer
(This definition makes use of the MonadLayer instance for
StateT provided by the layers package.)
Transformers as polymorphic layers
Every monad transformer can be made an instance of
MonadLayer, but not all monad layers are monad
transformers. For this reason it might be useful to have a subclass of
MonadLayer that represents all the monad layers which are monad
transformers. (A monad layer is a monad transformer if it is (parametrically)
polymorphic in its inner monad.) layers provides such a type class:
class (MonadLayer m, m ~ Outer m (Inner m)) => MonadTrans m where
type Outer m :: (* -> *) -> * -> *
This MonadTrans class is a bit different to the one from
transformers. Like MonadLayer (of which it is a subclass), it is
instantiated on monads (* -> *) rather than monad constructors
((* -> *) -> * -> *). It has an associated type
which points to the monad constructor from
which Outer mm is composed. The superclass equality constraint on the MonadTrans
class m ~ Outer m (Inner m) ensures that m really is a composition of the
monad constructor Outer m and the inner monad Inner m, making this class
pretty much equivalent to the MonadTrans class from transformers.
If GHC supported QuantifiedConstraints and ImplicationConstraints (which
it doesn't due to GHC bugs #2893 and #5927 respectively (though it
seems possible that these could be fixed some day)), it would be tempting to
reformulate MonadTrans to be instantiated directly on types t of kind
(* -> *) -> * -> *, just like the MonadTrans class from transformers.
To ensure that such a MonadTrans class would still be a "subclass" of
MonadLayer, it would need to have the constraint
(forall m. Monad m => (MonadLayer (t m), Inner (t m) ~ m)) in its superclass
context. However, this would exclude some instances which are currently
permitted. For example, if it was done right (and if there existed a
CommutativeMonad type class), the monad instance of
ListT would look something like
instance CommutativeMonad m => Monad (ListT m). This in turn would mean that
ListT's MonadLayer instance would have the constraint
CommutativeMonad m in its context, meaning that the constraint
forall m. Monad m => (MonadLayer (ListT m), Inner (ListT m) ~ m) would not
be satisfied, preventing ListT from being made an instance of MonadTrans.
This in turn could be solved by adding an associated type of kind
(* -> *) -> Constraint (which would default to Monad) to the MonadTrans
class and modifying the superclass constraint accordingly, but it would be
kind of messy. This is how it would look:
class (forall m. MonadConstraint t m => (MonadLayer (t m), Inner (t m) ~ m))) =>
MonadTrans t
where
type MonadConstraint t :: (* -> *) -> Constraint
type MonadConstraint t = Monad
Invariant functors
The MonadLayer and MonadTrans
classes we showed in the last two sections were not quite complete: we omitted
one method from each of them for the sake of the discussion. Here are the
complete definitions of both of these classes:
class (Monad m, Monad (Inner m)) => MonadLayer m where
type Inner m :: * -> *
layer :: Inner m a -> m a
layerInvmap
:: (forall b. Inner m b -> Inner m b)
-> (forall b. Inner m b -> Inner m b)
-> m a
-> m a
class (MonadLayer m, m ~ Outer m (Inner m)) => MonadTrans m where
type Outer m :: (* -> *) -> * -> *
transInvmap :: (MonadTrans n, Outer n ~ Outer m)
=> (forall b. Inner m b -> Inner n b)
-> (forall b. Inner n b -> Inner m b)
-> m a
-> n a
We've added the layerInvmap and
transInvmap operations to MonadLayer and MonadTrans
respectively. transInvmap corresponds exactly to the tmap function
described in the mmtl section above. (Its name refers to the fact that monad
transformers (should) be invariant functors in the category of monads.
(Re-read the mmtl section above if you've forgotten what this means.))
layerInvmap is like a restricted version of transInvmap where the inner
monad is fixed: this is because monad layers are not necessarily parametric in
their inner monad. It might then seem that layerInvmap is useless, but it
can still be useful to apply a transformation to the underlying monad which
does not change its type. For example,
is such a transformation. This
means that local flayerInvmap (unlike layer on its own) is powerful enough to
define a universal pass-through instance for any
MonadReader through any MonadLayer.
Functorial layers
While layerInvmap and transInvmap
are useful, they can only lift from the inner monad morphisms which have an
inverse. Sometimes we might want to lift morphisms which do not have an
inverse. However, not all monad layers/monad transformers are capable of
lifting such morphisms through them, so we need new subclasses of
MonadLayer and MonadTrans to
represent monad layers and monad transformers through which such morphisms
can be lifted. We call these classes MonadLayerFunctor
and MonadTransFunctor. (Their names refer to the fact
they represent functors in the category of monads.) The provide the operations
layerMap and transMap
respectively, the definitions of which are given below:
class MonadLayer m => MonadLayerFunctor m where
layerMap :: (forall b. Inner m b -> Inner m b) -> m a -> m a
class (MonadLayerFunctor m, MonadTrans m) => MonadTransFunctor m where
transMap :: (MonadTrans n, Outer n ~ Outer m)
=> (forall b. Inner m b -> Inner n b)
-> m a
-> n a
Peelable layers
Is it possible to lift control operations like
catch through a monad layer? Yes, if that
monad layer provides an instance of MonadLayerControl:
class MonadLayerFunctor m => MonadLayerControl m where
data LayerState m :: * -> *
restore :: LayerState m a -> m a
layerControl
:: ((forall b. m b -> Inner m (LayerState m b)) -> Inner m a)
-> m a
The design of MonadLayerControl is very similar to that of
MonadTransControl from the monad-control package (discussed above). StT
is renamed to LayerState, restoreT to
restore and liftWith to
layerControl. One of the main differences with
MonadLayerControl is that LayerState is parameterised by
m :: * -> * rather than t :: (* -> *) -> * -> * (as StT is). This is
simply because monad layers don't necessarily have a t. What LayerState m
is supposed to represent then is the portion of the monadic state of m that
is independent of .
Inner m
The other big difference between MonadLayerControl and monad-control's
MonadTransControl is with the run operation that layerControl passes to
its continuation. MonadLayerControl's run operation is of type
forall b. m b -> Inner m (LayerState m b): unlike MonadTransControl's,
ours is not polymorphic over all inner monads, so we cannot statically ensure
with the type system that there are no remaining side effects in m in the
action returned by run. The reason we make this change is because layers are
not necessarily parametric in their inner monad, so we cannot express for
monad layers the polymorphism used by monad-control's MonadTransControl.
However, we do include our own version of
MonadTransControl. It has the operation
transControl, which does exactly the same thing as
layerControl, but the run operation it passes to its continuation is more
polymorphic. We can use the Outer associated type of the
MonadTrans class (from which our MonadTransControl is (transitively)
descended) to express the polymorphism required to statically ensure that the
result of the run operation provided by transControl has no remaining side
effects in . We don't intend for Outer mMonadTransControl to ever really
be used in practice though. Its main use is as a guide for those who write
monad transformers. If you are implementing a monad transformer t, you'll
almost certainly want to make t an instance of both MonadLayerControl and
MonadTransControl. If it turns out that you can write an instance of
MonadLayerControl for t, but not an instance of MonadTransControl, then
you'll know that your instance for MonadLayerControl was invalid because it
relied on residual side effects in t in the result of its run computation.
Without MonadTransControl it is possible that such instances would be
distributed in libraries and propogated widely before anybody realised their
invalidity, because the type system cannot do so automatically with
MonadLayerControl alone. For monad layers which are not monad transformers,
we still have to just trust that their instances of MonadLayerControl do the
right thing, but in practice most monad layers are monad transformers, so this
is not really that bad. Here is the MonadTransControl type class:
class (MonadLayerControl m, MonadTrans m) => MonadTransControl m where
transControl
:: (forall n. (MonadTrans n, Outer n ~ Outer m)
=> (forall b. n b -> Inner n (LayerState n b))
-> Inner n a)
-> m a
In case it isn't obvious why MonadLayerControl is a subclass of
MonadLayerFunctor rather than MonadLayer, it's because it's possible to
define layerMap in terms of layerControl and restore. Given this fact,
MonadLayerControl not being a subclass of MonadLayerFunctor makes about as
much sense as Monad not being a subclass of Functor (given that fmap can
be defined in terms of >>= and return). Here is layerMap defined in
terms of layerControl and restore:
layerMap f m = layerControl (\run -> f (run m)) >>= restore
Zero-aware layer state
If layers just copies monad-control's design for
MonadLayerControl pretty much verbatim, and
monad-control's solution to the bracket problem produces incorrect results,
then how can layers hope to solve the bracket problem any better? The truth
is that once again, we omitted a method when we showed you MonadLayerControl
class for the sake of the discussion. The full MonadLayerControl class is as
follows:
class MonadLayerFunctor m => MonadLayerControl m where
data LayerState m :: * -> *
zero :: LayerState m a -> Bool
restore :: LayerState m a -> m a
layerControl
:: ((forall b. m b -> Inner m (LayerState m b)) -> Inner m a)
-> m a
zero _ = False
The only differnce is that we've added the operation
zero. It's very simple: it just takes the LayerState
of m and says whether or not it's "zero". For example, in the
MonadLayerControl instance for MaybeT, zero
is pretty much just isNothing. For
ListT, zero is null. A good rule of
thumb for implementing zero for a monad layer is to ask, for a given
LayerState m a, does this LayerState m a contain a value of type a
somewhere that can be extracted from it? If the answer is no, then zero
should return True. For example, MaybeT's zero returns True when its
LayerState is Nothing, and ListT's zero returns True when
LayerState is []. This allows us to solve the bracket problem without
discarding the monadic state of the outer layers, because we can directly
detect when a short-circuiting monad transformer has short-circuited
("zero'd"). layers defines its version of
bracket in terms of the
MonadTry interface, which is shown here:
class MonadMask m => MonadTry m where
mtry :: m a -> m (Either (m a) a)
mtry = liftM Right
It provides a single operation mtry, which takes
a monadic action in m and returns a new monadic value in m which is
guaranteed not to short-circuit. If the action m that was given to mtry
would have short-circuited, it returns Left m, otherwise it returns
Right a, where a is the value returned by the computation m.
(The MonadMask you see in the superclass
constraint is for dealing with asynchronous exceptions. MonadMask is
actually not used anywhere in the
MonadException interface, only by
MonadTry, for defining bracket and friends. This seems to suggest that
mask/bracket and throw/catch are actually orthogonal to one
another.)
Instances of MonadTry are provided for all the base monads defined in the
base and transformers libraries, and the zero operation of
MonadLayerControl is used to define a universal pass-through instance for
any MonadTry wrapped by a monad layer:
instance (MonadLayerControl m, MonadTry (Inner m)) => MonadTry m where
mtry m = do
ma <- layerControl (\run -> mtry (run m))
case ma of
Left m' -> return . Left $ layer m' >>= restore
Right a -> if zero a
then return . Left $ restore a
else liftM Right $ restore a
Let's try out the bracket function defined in Control.Monad.Interface.Try.
Let's see if it produces the correct result where monad-control could not.
bar :: IO (Maybe (), Int)
bar = flip runStateT 0 $ runMaybeT $ bracket
(do
lift $ putStrLn "init"
modify (+1))
(\() -> do
modify (+16)
lift $ putStrLn "close"
modify (+32)
mzero
modify (+64))
(\() -> do
modify (+2)
lift $ putStrLn "body"
modify (+4)
mzero
modify (+8))
>>>barinit body close (Nothing,55)
This is the correct result! Our bracket correctly handles the
short-circuiting monad transformer without discarding the monadic state of the
other transformers. The bracket problem is solved.
Fully modular monad interfaces
We said above that one of the problems with the type class machinery provided
by (and the design patterns suggested by) transformers and mtl is the lack
of modularity possible with them. If you have n monad interfaces and m
monad layers, then you need O(n * m) instances. You need to write an
instance for each combination of monad transformer and monad interface. In
particular, this makes it impossible to use a third-party monad interface with
a third-party monad transformer, unless the author of the monad transformer
knew of that monad interface and was okay with making their monad transformer
depend on the package which provides that monad interface (or vice-versa). We
would like for there to only need to be O(n + m) instances, and for it to be
possible to use third-party monad interfaces with third-party monad
transformers that know nothing about each other. The achieve this, we need
what this documentation has so far referred to as "universal pass-through
instances". We've shown a few examples of these already, but here is another
one just to make it completely clear what we're talking about (this time for
MonadCont):
instance (MonadLayerControl m, MonadCont (Inner m)) => MonadCont m where
callCC f = layerControl (\run -> callCC $ \c -> run . f $ \a ->
layer (run (return a) >>= c)) >>= restore
As we said above, there are two reasons why this is not done currently: one is
that the lift operation provided by transformers
is not powerful enough to define universal pass-through instances for control
operations. layers solves this by with the
MonadLayerControl interface, whose
layerControl operation can lift control operations (such
as callCC above). The other reason why this is
not done is because these instances require the OverlappingInstances
extension. layers doesn't solve this problem, but we just say "fuck it"
and use OverlappingInstances anyway.
It seems that the main reason that OverlappingInstances is considered
evil is that if your code uses relies on an overlapping instance, it's
possible for the meaning of your code to change if you import a module which
defines a more specific instance which matches the types in your code and
the more specific instance behaves differently to the more general instance.
While this is definitely bad, we don't think that it will be a problem in
practice with layers, as long as people use common sense when writing
instances. In order to spell out exactly what we mean by "common sense", let
us first note that there are three types of instances that are needed when
writing monad transformers and/or monad interfaces the layers way.
- Monad transformers need to be made instances of
MonadLayerandMonadTrans, and if applicable,MonadLayerFunctor,MonadTransFunctor,MonadLayerControlandMonadTransControl. UsingStateTas an example, these instances have the forminstance Monad m => MonadLayer (StateT s m) where type Inner (StateT s m) = m. - The functionality provided by a monad transformer is often abstracted
into a monad interface. Again using the example of
StateT(andMonadState), these instances have the forminstance Monad m => MonadState s (StateT s m). - To achieve the level of modularity we wish for, monad interfaces need
to have universal pass-through instances. This means that an monad which
is an instance of a given monad interface is still an instance of that
monad interface when wrapped by a monad layer. These instances have the
form:
instance (MonadLayer m, MonadState s (Inner m)) => MonadState s m. (These are also the only instances that require theOverlappingInstancesextension.)
Now that we can easily refer to these different types of instances by number,
the "common sense" rules that consumers of the layers library need to
follow to ensure sensible behaviour are as follows:
- Every instance of type 3, e.g., the universal pass-through instance for a given monad interface, must be defined in the same module as that monad interface. (The only thing worse than overlapping instances are overlapping orphan instances.)
- A monad transformer
t's instances of type 2, i.e., of the monad interfaces which encapsulate its functionality, can be defined in either the same module ast's type 1 instances or in the same module(s) as the monad interface(s), but if the latter, then thatthose module(s) must/ also import the module in whicht's type 1 instances are defined. (Normally this is not an issue becauset's instances of type 1 are defined in the same module ast(or in the same module asMonadLayerin the special case of the monad transformers from thetransformerspackage), but ift's type 1 instances are orphans then this is relevant.) - You should generally not need to define instances for monad layers that
do not fit into one of the three types above. One exception is if you are
writing a monad transformer
t, and you want instances of the monad interfacemto be able to pass throught, but you know that the universal pass-through instance ofmcan only pass-through monad layers which can provideMonadLayerControl, but yourtcan't, yet you know of a way to define a pass-through instance ofmfort. This type of instance is not a problem. The same rules which apply to type 2 instances (these rules are described rule 2 above) apply to these kinds of instances, - The only other case where you might want to define an instance that
doesn't fall into one of the three types described above is where you are
writing a monad transformer
t, and you want instances of the monad interfacemto be able to pass throught, and the universal pass-through instance ofmis able to pass throught, but you have a much more efficient implementation of the pass-through ofmthroughtthan the universal one. The same rules which apply to type 2 instances (described in rule 2 above) apply to these kinds of instances, with the additional stipulation that you must make absolutely certain that such instances behaves exactly the same as the universal ones. This is to ensure that the behaviour of a program which uses the universal instance does not change if it switches to the optimised one. However, we strongly recommend against providing such instances unless you are sure that the inefficiency of the universal pass-through instance is causing a noticable degradation in performance.
Having said all of that, there are real problems caused by
OverlappingInstances that makes layers harder to use than it should be.
These are mainly the error messages produced by GHC when it cannot find a
solution for constraint which has overlapping instances. For example,
attempting to compile the following program produces the following error
message:
import Control.Monad.Interface.State stupidGet :: Monad m => m Int stupidGet = get
>>>runhaskell Main.hsMain.hs:4:9: Overlapping instances for MonadState Int m arising from a use of `get' Matching instances: instance [overlap ok] (Control.Monad.Layer.MonadLayer m, MonadState s (Control.Monad.Layer.Inner m)) => MonadState s m -- Defined in `Control.Monad.Interface.State' instance [overlap ok] (Monad m, Data.Monoid.Monoid w) => MonadState s (Control.Monad.Trans.RWS.Strict.RWST r w s m) -- Defined in `Control.Monad.Interface.State' instance [overlap ok] (Monad m, Data.Monoid.Monoid w) => MonadState s (Control.Monad.Trans.RWS.Lazy.RWST r w s m) -- Defined in `Control.Monad.Interface.State' instance [overlap ok] Monad m => MonadState s (Control.Monad.Trans.State.Strict.StateT s m) -- Defined in `Control.Monad.Interface.State' instance [overlap ok] Monad m => MonadState s (Control.Monad.Trans.State.Lazy.StateT s m) -- Defined in `Control.Monad.Interface.State' (The choice depends on the instantiation of `m' To pick the first instance above, use -XIncoherentInstances when compiling the other instance declarations) In the expression: get In an equation for `stupidGet': stupidGet = get
Annoyingly, GHC unhelpfully suggests that we enable the IncoherentInstances
extension. Ideally the error message that GHC would produce would simply be:
>>>runhaskell Main.hsMain.hs:4:9: Could not deduce (MonadState Int m) arising from a use of `get' from the context (Monad m) bound by the type signature for myGet :: Monad m => m Int at Main.hs:3:10-25 Possible fix: add an instance declaration for (MonadState Int m) In the expression: get In an equation for `stupidGet': stupidGet = get
We are unsure whether this could be considered a bug in GHC or whether it's expected behaviour, but either way we are documenting it here so that users who run into this problem can know what to do when they see it.
(Sometimes similar messages pop up but are caused by the monomorphism restriction. If you think your types really do satisfy the constraints in question, try either disabling the monomorphism restriction or adding type signatures where appropriate.)
Compatibility with transformers
One of the fatal flaws of earlier attempts to do what we have done with the
layers library, such as the modular monad transformer library mmtl, is
that they defined their own versions of the monad transformers from
transformers. As we have said already in this document, the types defined in
the transformers package are used everywhere in the Haskell ecosystem, to
the extent that it's simply not possible that a library which seeks to improve
the type class machinery for working with compositions of those types will be
adopted unless it reuses those types themselves. layers gets this right by
reusing the monad transformer and base monad types from the transformers
package and not defining any of its own.
However, almost as widespread in the Haskell ecosystem as the monad
transformers defined in the transformers package are the monad interfaces
defined in the mtl package. We unfortunately can't reuse these monad
interfaces in the layers package, because we need monad interfaces to have
universal pass-through instances, and the mtl monad interfaces do not
provide universal pass-through instances. Rule 1 above states that we are only
allowed to define a universal pass-through instance for a given monad
interface in the same module module as that monad interface, so we can't
define them ourselves, and we have no control over modules in mtl package
in which those interfaces are defined.
The solution the layers package takes is to define its own versions of all
of the monad interfaces from the mtl package (as well as some new ones too).
However, this isn't nearly as bad as defining our own versions of all of the
monad transformers from the transformers package. The reason for that is
that a constraint MonadState' s m (where MonadState' is the MonadState
defined in the mtl package) should be more or less equivalent to the
constraint in the sense that
the set of monads MonadState s mm that satisfy the former should be more or less the same
as the set of monads m which satisfy the latter. In particular, a monad m
built from a stack of monad transformers from the transformers library which
satisifes some constraint composed of monad interfaces from the mtl package,
is guaranteed to satisfy corresponding constraint composed of equivalent monad
interfaces from the layers package.
Polymorphic, universal lifts
Notice that we renamed what is called lift in
transformers to layer. This is because one of the
goals of layers is to unify all operations which are called lift (e.g.,
liftIO, liftBase and lift itself) into a single,
universal lift. The lift operation from the
MonadTrans interface is a little different to
the other lifts however, and we felt we should change the name to reflect that.
A more accurate name would be wrap, or as we call it, layer. Take liftIO
as an example. What we mean when we use liftIO is "Listen, monad, I know
you know how to do IO. I don't care if IO is one monad beneath the top of
your stack or ten, I don't even care if you are IO itself, I just know you
understand IO and that you can figure out the rest yourself." liftBase is
generally used similarly. lift on the other hand means "wrap this monadic
action in exactly one monad transformer". A useful operation no doubt, but
it isn't really the same thing.
Anyway, if liftBase means "lift this operation from the base of the monad
stack through an arbitrary number of monad transformers", and liftIO is a
special case of liftBase where the base monad is always IO, is it possible
to define a lift that can lift operations from any monad anywhere in the
stack? If so, then as well as being more powerful than liftBase, it would
also be able to do what layer does, because another way of understanding
layer is as an operation which means "lift this operation from the monad
just beneath the top of the stack", and a lift which can lift operations
from any monad anywhere in the stack can surely lift operations from the
monad just beneath the top of the stack as well. layers provides such a
lift function, using type class MonadLift:
class (Monad i, Monad m) => MonadLift i m where
lift :: i a -> m a
instance Monad m => MonadLift m m where
lift = id
instance (MonadLayer m, MonadLift i (Inner m)) => MonadLift i m where
lift = layer . lift
(In other words, a monadic operation in the monad i can be lifted into
the monad m if i = m, or if m is a layer and operations from i can be
lifted into the inner monad of m.) Let's demonstrate why layers' lift is
nice. Imagine you have the following code (using transformers' lift):
main = flip evalStateT 0 $ do
modify (+1)
x <- get
lift $ print x
>>>runhaskell Main.hs1
Perfect! But now let's say that for some reason you wanted to add a load of
redundant IdentityT transformers to your monad
stack.
main = flip evalStateT 0 $ runIdentityT $ runIdentityT $ runIdentityT $ do
modify (+1)
x <- get
lift $ print x
>>>runhaskell Main.hsMain.hs:6:58: Couldn't match type `IO' with `Control.Monad.Trans.Identity.IdentityT (Control.Monad.Trans.Identity.IdentityT (Control.Monad.Trans.State.Lazy.StateT b0 m0))' Expected type: Control.Monad.Trans.Identity.IdentityT (Control.Monad.Trans.Identity.IdentityT (Control.Monad.Trans.State.Lazy.StateT b0 m0)) () Actual type: IO () In the second argument of `($)', namely `runIdentityT $ do { modify (+ 1); x <- get; lift $ print x }' In the second argument of `($)', namely `runIdentityT $ runIdentityT $ do { modify (+ 1); x <- get; lift $ print x }' In the second argument of `($)', namely `runIdentityT $ runIdentityT $ runIdentityT $ do { modify (+ 1); x <- get; lift $ print x }'
What an ugly error message! It is the result of the lift from transformers
not being polymorphic enough. This means that in practice, adding an
IdentityT transformer in the middle of an existing transformer stack breaks
code written for that stack. This seems wrong because adding (or removing)
IdentityT from a transformer stack should be a no-op. To get the above code
working using transformers' lift, we would have to change lift to
lift . lift . lift . lift, which is still brittle and prone to breakage if
we in any way modify the transformer stack again. layers' polymorphic lift
does not have this problem. If we change the lift in the above code to be
the lift from layers rather than from transformers, it just works:
>>>runhaskell Main.hs1
In addition to lift, which is not very powerful by itself, layers also
defines liftInvmap, liftMap and
liftControl, which are analogous to
layerInvmap, layerMap and
layerControl respectively. All of these are trivial
except for liftControl, which doesn't work in quite the same way as its
layerControl counterpart. It is defined as follows:
class MonadLiftFunctor i m => MonadLiftControl i m where
liftControl :: ((forall b. m b -> i (m b)) -> i a) -> m a
instance Monad m => MonadLiftControl m m where
liftControl f = f (liftM return)
instance (MonadLayerControl m, MonadLiftControl i (Inner m)) =>
MonadLiftControl i m
where
liftControl f = layerControl $ \runLayer -> liftControl $ \run ->
f $ liftM (\m -> layer (lift m) >>= restore) . run . runLayer
Unlike MonadLayerControl,
MonadLiftControl doesn't use an associated data type
for the layer state. Part of the reason for this is that associated data types
and overlapping instances do not play well together (see GHC bug #4259).
However, we still copied monad-control - just an earlier version!
monad-control 0.2 did not use associated data types either, and if you look
at version 0.3 the transformers-base which depended on it, you will find a
type class MonadBaseControl which is pretty much exactly equivalent to
MonadLiftControl, except for the fact that MonadLiftControl is not just
restricted to the base monad of a transformer stack.
References
- Control.Monad.Trans.Class
-
From the
transformerspackage, this module contains the canonical implementation of monad transformers in Haskell. ItsMonadTranstype class unfortunately only provides theliftoperation, which can only lift very simple types of operations through a monad transformer. In particular it can't lift what are called "control" operations. See: http://hackage.haskell.org/packages/archive/transformers/0.3.0.0/doc/html/Control-Monad-Trans-Class.html. - Control.Monad.IO.Class
-
From the
transformerspackage, this module defines theMonadIOinterface with itsliftIOoperation.layersprovides a much more powerful universalliftoperation which replacesliftIOand other similar operations. See: http://hackage.haskell.org/packages/archive/transformers/0.3.0.0/doc/html/Control-Monad-IO-Class.html. - Control.Monad.Base
-
From the
transformers-basepackage, this module defines theMonadBaseinterface with itsliftBaseoperation, which is a slight generalisation of theMonadIOinterface provided bytransformers. Theliftoperation provided bylayersis even more general thanliftBase, butliftBaseis still an improvement onliftIO. See: http://hackage.haskell.org/packages/archive/transformers-base/0.4.1/doc/html/Control-Monad-Base.html - Control.MFunctor
-
The
layerMap,transMapandliftMapoperations (from theMonadLayerFunctor,MonadTransFunctorandMonadLiftFunctorclasses respectively) are inspired by thehoistoperation from theMFunctorclass in thepipespackage. See: http://hackage.haskell.org/packages/archive/pipes/3.1.0/doc/html/Control-MFunctor.html. - Control.Monad.Trans
-
The
layerInvmap,transInvmapandliftInvmapoperations (from theMonadLayer,MonadTransandMonadLiftclasses respectively) are inspired by thetmapoperation from theMonadTransclass in themmtlpackage. See: http://hackage.haskell.org/packages/archive/mmtl/0.1/doc/html/Control-Monad-Trans.html. - Data.Functor.Invariant
-
From the
invariantpackage, this module implements invariant functors from the categoryHask. This is not used anywhere in thelayerspackage, but the "invmap" name of the member operation of theInvarianttype class is reused bylayersfor describing functors from the category of monads. See: http://hackage.haskell.org/packages/archive/invariant/0.1.0/doc/html/Data-Functor-Invariant.html - MonadCatchIO, finally and the error monad
-
Michael Snoyman posts an example to the Haskell Cafe mailing list of
MonadCatchIO'sbracketoperation producing incorrect results in the presence of short-circuiting monad transformers. See: http://www.haskell.org/pipermail/haskell-cafe/2010-October/084890.html - Control.Monad.Trans.Control
-
From the
monad-controlpackage, this module provides theMonadTransControlclass, which represents the class of monad transformers through which control operations can be lifted. The design of theMonadLayerControlandMonadTransControlclasses fromlayersare based on the design of theMonadTransControlclass from the most recent version ofmonad-control. See: http://hackage.haskell.org/packages/archive/monad-control/0.3.2/doc/html/Control-Monad-Trans-Control.html - Issue with monad-control
-
Leon Smith posts an example to the Snap Framework mailing list of
lifted-base'sbracketoperation (which is based onmonad-control) producing incorrect results. See: http://permalink.gmane.org/gmane.comp.lang.haskell.snapframework/1574 - #2893 (Implement "Quantified contexts" proposal)
-
GHC lacks support for
QuantifiedConstraints. WithQuantifiedConstraints, it would be possible to useforallin constraint expressions. This bug, along with #5927, is what is preventing us from refactoringMonadTransto be instantiated directly on types of kind(* -> *) -> * -> *. See: http://hackage.haskell.org/trac/ghc/ticket/2893. - #5927 (A type-level "implies" constraint on Constraints)
-
GHC lacks support for
ImplicationConstraints. WithImplicationConstraints,=>would be a type level operator with the kindConstraint -> Constraint -> Constraint. This bug, along with #2893, is what is preventing us from refactoringMonadTransto be instantiated directly on types of kind(* -> *) -> * -> *. See: http://hackage.haskell.org/trac/ghc/ticket/5927. - Control.Monad.Exception
-
This module from the
monad-abort-fdpackage contains theMonadMaskinterface on whichlayers'MonadMaskinteface is based. See: http://hackage.haskell.org/packages/archive/monad-abort-fd/0.3/doc/html/Control-Monad-Exception.html - Are there any good use cases for OverlappingInstances?
-
A Stack Overflow thread about the dangers of the
OverlappingInstancesextension, on which this package relies heavily. See: http://stackoverflow.com/questions/12628700/are-there-any-good-use-cases-for-overlappinginstances. - Haskell: Overlapping instances
-
Another Stack Overflow thread about the
OverlappingInstancesextension. What's interesting about this one is that the OP's motivating example is pretty much identical to theMonadLifttype class thatlayersdefines. See: http://stackoverflow.com/questions/1064232/haskell-overlapping-instances - Control.Monad.Base.Control
-
From version 0.3 of the
transformers-basepackage. The design of theMonadLiftControlclass is copied from the design ofMonadBaseControlclass in this module. See: http://hackage.haskell.org/packages/archive/transformers-base/0.3/doc/html/Control-Monad-Base-Control.html. - #4259 (Relax restrictions on type family instance overlap)
-
Part of the reason why we base the design of
MonadLiftControlon the old version ofMonadBaseControlfromtransformers-base0.3 rather than the current version inmonad-controlis because associated type families (which are used in the new version ofMonadBaseControl) don't play well withOverlappingInstances(which we want to use withMonadLiftControl). There is a bug report on GHC's bug tracker that is (sort of) about this problem. See: http://hackage.haskell.org/trac/ghc/ticket/4259.