MTL's Unfortunate MT Anchor
Haskell programming techniques were set back by mtl being associated with monad transformers.— Anthony Cowley (@a_cowley) July 14, 2016
MT = Meh Types
Monad Transformers are not a universally popular construction in Haskell. Without veering into tutorial territory, they are a layering of type constructors that permit one to write functions that operate on the different layers while retaining a monadic API with which the whole may be manipulated. The downsides to monad transformers as provided by the transformers package are that they,
- Are not commutative
- Can impose some overhead due to traversing the layers
- Don't scale painlessly from an API perspective as it can be hard to distinguish two similar layers (e.g. two layers of
ReaderT Int (ReaderT Int m) a)
- Layer navigation with explicit
liftapplications is brittle
MTL's Value Proposition
mtl package directly addresses the last two points above, and incidentally offers an amelioration of the second. The primary benefit of the
mtl is that it hides the concrete (monad transformer) type, instead offering a type class API that frees the programmer from writing out
lift applications, thereby making code prettier and more amenable to changes in the specific type used. Sometimes, after adopting the mtl style of listing the constraints the supplied type must satisfy, one will flatten a stack of type constructors into one
ApplicationState (name to taste) record capable of serving all needs.
MTL in Pop Culture
An interesting non-code linguistic idiom has grown popular in the Haskell community: references to "mtl style" (example: the previous paragraph). This phrase can mean something as specific as precisely what the
mtl package does, but in its loosest form it refers simply to the use of a type class constraint in preference to a concrete type in order to avoid writing type signatures that are coupled too closely to a type design that one might prefer to have some freedom to change.
I'll draw another loose connection to the
makeClassy suite of functionality in the lens package. This approach is great because it lets you nest data structures and transparently make use of code written without any awareness of that nesting. For instance, you define a
Person data type, write some functions for working with values of that type, then define a
Citizen record that has a field of type
Person, and can immediately use lens-y code written against the
Person type on values of the
Avoiding the Mainstream
While type classes provide a mechanism for the use of shared identifiers, willy nilly use of this approach has been socially discouraged because differences in meaning between uses of a shared name are a rich source of bugs and confusion. Haskellers love
fmapping things because there are a couple equations they can rely on no matter what they are
fmapping. Lenses bring with them the lens laws whose foundation is a promise not to immediately shoot you in the foot, and perhaps it is this bare minimum that lifts
makeClassy out of the semantics-free zone.
All of this is quite reminiscent of subtyping and interfaces (or protocols) in more mainstream, object-oriented languages. Programming to an interface is not something Haskell generally excels at outside of type classes, due in part to the very minimal module system it provides. By turning our backs on type classes with no claims to a mathematical foundation, we have wed ourselves too closely to concrete types. We astronauts of abstraction have forsaken an available abstraction to protect ourselves from those who would roughly poke us with it.
mtl package is carefully constructed, but I think informal uses of the mtl style phrase is a telling symptom that the benefits of the broad strokes of the
mtl design are under-appreciated. My conjecture is that while this is in part due to the noble pursuit of avoiding lawless type classes, it is also due to the fact that people just don't like monad transformers, and therefore don't have any great love for the
mtl. If the
mtl was a part of every Haskell program, rewriting a type signature from
Foo -> Bar
HasFoo t => t -> Bar
would be a borderline reflexive mechanical refactoring.