Why aren't monad transformers constrained to yield monads? - haskell

In the MonadTrans class:
class MonadTrans t where
-- | Lift a computation from the argument monad to the constructed monad.
lift :: Monad m => m a -> t m a
why isn't t m constrained to be a Monad? i.e., why not:
{-# LANGUAGE MultiParamTypeClasses #-}
class Monad (t m) => MonadTrans t m where
lift :: Monad m => m a -> t m a
If the answer is "because that's just the way it is", that's fine -- it's just confusing for a n008.

You suggested the following:
class Monad (t m) => MonadTrans t m where
lift :: Monad m => m a -> t m a
...but does that really mean what you want? It seems you want to express something like "a type t may be an instance of MonadTrans if, for all m :: * -> * where m is an instance of Monad, t m is also an instance of Monad".
What the class definition above actually says is more like "types t and m may constitute an instance of MonadTrans if, for those specific types, t m is an instance of Monad". Consider carefully the difference, and the implied potential for instances that may not be what you'd want.
In the general case, every parameter of a type class is an independent "argument", a fact which has been a bountiful source of both headaches and GHC extensions as people have attempted to use MPTCs.
Which isn't to say that such a definition couldn't be used anyway--as you point out, the current definition is not ideal either. The age-old problem "Why Data.Set Is Not a Functor" is related, and such issues helped motivate the recent ConstraintKinds tomfoolery.
The ultimate answer to "why not" here is almost certainly the one given by Daniel Fischer in the comments--because MonadTrans is pretty core functionality, it would be undesirable to make it depend on some terrifying cascade of increasingly arcane GHC extensions.

Related

Why no MonadWriter instance for ParsecT?

I was writing some Haskell earlier today. Came up with something along the lines of
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Foo a = Foo { ParsecT String () (Writer DefinitelyAMonoid) a }
deriving (Functor, Applicative, Monad, MonadWriter DefinitelyAMonoid)
This did not compile. "No instance for (MonadWriter DefinitelyAMonoid (ParsecT String () (Writer DefinitelyAMonoid))) arising from the 'deriving' clause of a data type declaration", GHC told me. So I decided to see if some other MTL classes would work, and they did. Reader and MonadReader, and Writer and MonadWriter. So I turned was directed by a Discord user to Hackage, where the problem was clear:
MonadState s m => MonadState s (ParsecT s' u m)
MonadReader r m => MonadReader r (ParsecT s u m)
MonadError e m => MonadError e (ParsecT s u m)
No MonadWriter instance can make it through the ParsecT transformer! Seemed for all the world to me like a mere oversight, and I just barely realized before opening an issue on Github that this might be deliberate. So I took a look at Megaparsec:
(Stream s, MonadState st m) => MonadState st (ParsecT e s m)
(Stream s, MonadReader r m) => MonadReader r (ParsecT e s m)
(Stream s, MonadError e' m) => MonadError e' (ParsecT e s m)
Again, no MonadWriter.
Why do neither Parsec nor Megaparsec provide a MonadWriter instance for ParsecT? Is there something special about parser monads like these that keep them from playing nice with writers? Is there something similar to this going on?
Parsec is a backtracking monad. While the MonadWriter instance you propose is not impossible, it's a strange thing to put a Writer underneath a backtracking monad. This is because the inner layers of a monad stack provide the "foundational" effects upon which the outer layers build -- that is, whatever ParsecT u (Writer w) does, it must do so only in terms of tell. So if it tells a value in a branch that is later backtracked out of, there is no possibility to "untell" that value, and so the Writer's result will be more like a trace of the parsing algorithm rather than a trace of the dataflow abstraction that Parsec is presenting. It's not useless, but it's pretty odd, and you would have much more natural semantics if WriterT were on the outside and Parsec on the inside.
The same argument applies to State. Parsec exposes both its own user state functionality (the u parameter) and a MonadState instance that inherits the underlying monad's state functionality. The latter comes with the same caveats as MonadWriter -- the statefulness follows the trace of the algorithm, not the dataflow. I don't know why this instance was included while the Writer instance was not; they are both tricky in the same way.
-- I'm presuming the user might want a separate, non-backtracking
-- state aside from the Parsec user state.
instance (MonadState s m) => MonadState s (ParsecT s' u m) where
get = lift get
put = lift . put
Parsec's user state, on the other hand, follows the dataflow, not computation, and it has the same effect as putting StateT on the outside. It always seemed odd that Parsec provides its own state facility rather than just asking us to use a transformer -- but, thinking about it now, I suspect it is just to avoid having to put lift all over the place if you do happen to use state.
In conclusion, they could provide the MonadWriter instance you ask for, but I think there is a decent reason not to -- to discourage making the mistake that I think you are probably making.

Does Higher order polymorphism require strict order of arguments?

Reading LYAH, I stumbled upon this piece of code:
newtype Writer w a = Writer { runWriter :: (a, w) }
instance (Monoid w) => Monad (Writer w) where
return x = Writer (x, mempty)
(Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')
While trying to understand what the heck is Writer w in the first line, I discovered this not being a full type, but a sort of type constructor with 1 argument, like Maybe for Maybe String
Looks great, but what if the initial type if Writer' is defined with swapped type arguments, like this:
newtype Writer' a w = Writer' { runWriter :: (a, w) }
Is it possible to implement Monad instance now? Something like this, but what could actually be compiled:
instance (Monoid w) => Monad (\* -> Writer' * monoid) where
The idea of \* -> Writer' * monoid is the same as Writer w :
A type constructor with one type argument missing -- this time first one.
This is not possible in Haskell, what you'd need is a type-level lambda function, which does not exist.
There are type synonyms which you can use to define reorderings of type variables:
type Writer'' a w = Writer' a w
but you can not give class instances for partially applied type synonyms (even with the TypeSynonymInstances extension).
I wrote my MSc thesis about the subject of how type-level lambdas can be added to GHC: https://xnyhps.nl/~thijs/share/paper.pdf to be used in type-class instances without sacrificing type inference.
What you are seeing here is a parochial design choice of Haskell. It makes perfect sense, conceptually speaking, to say that your Writer' type is a functor if you "leave out" its first parameter. And a programming language syntax could be invented to allow such declarations.
The Haskell community hasn't done so, because what they have is relatively simple and it works well enough. This isn't to say that alternative designs aren't possible, but to be adopted such a design would have to:
Be no more complex to use in practice than what we already have;
Offer functionality or advantage that would be worth the switch.
This generalizes to many other ways that the Haskell community uses types; often the choice to represent something as a type distinction is tied to some artifact of the language's design. Many monad transformers are good examples, like MaybeT:
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
instance Functor m => Functor (MaybeT m) where ...
instance Applicative m => Applicative (MaybeT m) where ...
instance Monad m => Monad (MaybeT m) where ...
instance MonadTrans MaybeT where ...
Since it's a newtype, this means that MaybeT IO String is isomorphic to IO (Maybe String); you can think of the two types as being two "perspectives" on the same set of values:
IO (Maybe String) is an IO action that produces values of type Maybe String;
MaybeT IO String is a MaybeT IO action that produces values of type String.
The difference between the perspectives is that they imply different implementations of the Monad operations. In Haskell then this is also tied to the following parochial technical facts:
In one String is the last type parameter (the "values") and in the other Maybe String is;
IO and MaybeT IO have different instances for the Monad class.
But maybe there is a language design where you could say that the type IO (Maybe a) can have a monad specific to it, and distinct from the monad for the more general IO a type. That language would incur some complexity to make that distinction consistently (e.g., rules to determine which Monad instance to by default for IO (Maybe String) and rules to allow the programmer to override the default choice). And I'd wager modestly that the end result would be no less complex than what we do have. TL;DR: Meh.

How to use a typeclass like `HasDynFlags m` in GHC

While playing with GHC code base, I find a typeclass named HasDynFlags:
class HasDynFlags m where
getDynFlags :: m DynFlags
Although the typeclass name looks self-explanatory, I couldn't find other
constraints in the typeclass definition that says m has to be Monad or at least Functor so we can get access to that value.
However, most use of it I find in the code base is inside a do-notation, e.g dynFlag <- getDynFlags where m is further constrainted to be an instance of Monad.
My questions are:
For HasDynFlags m, does m have to be at least Functor to make this typeclass useful?
If the answer to the first question is no, then how are we supposed to get access to a value of DynFlags given getDynFlags :: m DynFlags, without any further knowledge about m?
According to the class definition,
class HasDynFlags m where
getDynFlags :: m DynFlags
m is satisfied by kind (* -> *). The kind (* -> *) is implied by the type m DynFlags, which demonstrates that m is a type constructor taking exactly one type parameter.
There are no further constraints on m here. Specifically, the resulting type needn't be a Functor (or Monad), although given common naming conventions for type variables in Haskell, there's a good chance Monad is the motivating case.
EDIT: To answer the second question, the Functor or Monad class constraints we expect are introduced in more specific contexts. For example, consider the type,
(HasDynFlags m, Monad m) => m DynFlags
I think that's all there is to it.

MonadTransControl instance for ProxyFast/ProxyCorrect

Using pipes, I'm trying to write an instance of MonadTransControl for the ProxyFast or ProxyCorrect type. This is what I've got:
instance MonadTransControl (ProxyFast a' a b' b) where
data StT (ProxyFast a' a b' b) a = StProxy { unStProxy :: ProxyFast a' a b' b Identity a}
liftWith = undefined
restoreT = undefined
I have no idea how to write liftWith or restoreT. The instances for the other monad transformers all use a function that "swaps" the monads, for example EitherT e m a -> m (EitherT e Identity a), but I couldn't find any such function in pipes. How does the instance for MonadTransControl for ProxyCorrect / ProxyFast look like? Or is it impossible to write one? (If yes, is it possible in pipes 4.0?)
Thanks for the link, and now I can give a better answer.
No, there is no way to implement this, using either version of pipes. The reason why is that MonadTransControl expects a monad transformer to be built on top of a single layer of the underlying base monad. This is true for all the monad transformers that MonadTransControl currently implements, such as:
ErrorT ~ m (Either e r)
StateT ~ s -> m (r, s)
WriterT ~ m (r, w)
ReaderT ~ i -> m r
ListT ~ m [r] -- This version of ListT is wrong, and the true ListT
-- would not work for `MonadTransControl`
However, a Proxy does not wrap a single layer of the base monad. This is true for both pipes versions where you can nest as many layers of the base monad as you want.
In fact, any monad transformer that nests the base monad multiple times will defy a MonadTransControl instance, such as:
FreeT -- from the `free` package
ListT -- when done "right"
ConduitM -- from the `conduit` package
However, just because pipes does not implement MonadTransControl doesn't mean that all hope is lost. pipes-safe implements many of the operations that one would typically expect from MonadTransControl, such as bracketing resource acquisitions, so if you can elaborate on your specific use case I can tell you more if there is an appropriate pipes-based solution for your problem.

Anatomy of a monad transformer

I'm trying to learn monad transformers, based on the standard Haskell libraries (mtl? transformers? not sure which one came with my download of the Haskell platform - 7.4.1).
What I believe I've noticed is a common structure for each monad transformer definition:
base type ('Base')
Monad instance
transformer type ('BaseT')
Monad instance
MonadTrans instance
MonadIO instance
transformer class ('MonadBase')
some operations
instances for other 'BaseT's
So for example, for the Writer monad, there'd be:
a Writer datatype/newtype/type, with a Monad instance
a WriterT datatype/newtype/type, with Monad, MonadTrans, and MonadIO instances
a MonadWriter class, and instances of this class for StateT, ReaderT, IdentityT, ...
Is this how monad transformers are organized? Am I missing anything/do I have any incorrect details?
The motivation for this question is figuring out:
what the relationships and differences are between the "BaseT"s and the corresponding "MonadBase"s and "Base"s
whether all three are required
how MonadTrans is related and what its purpose is
mtl package doesn't implement monad transformers. At least WriterT is just reexported from transformers.
transformers package implements WriterT, which is a monad transformer itself. Writer is just an alias:
type Writer w = WriterT w Identity
Some libraries can implement Writer separately, but anyway it is just a special case of WriterT. (Identity is a trivial monad, it doesn't have any additional behavior.)
MonadTrans allows you to wrap underlying monad into the transformed one. You can live without it, but you will need to perform manual wrapping (see MonadTrans instance definition for WriterT for example how to do it). The only use case where you really need MonadTrans -- when you don't know actual type of transformer.
MonadWriter is a type class declared in mtl. It's methods (writer, pass, tell and listen) are the same as function for WriterT. It allows to wrap (automatically!) WriterT computation through stack of transformers, even if you don't know exact types (and even number!) of transformers in the stack.
So, WriterT is the only type which is "required".
For other monad transformers it is the same: BaseT is a transformer, Base is a monad without underlying monad and MonadBase is a type class -- class of all monads, that have BaseT somewhere in transformers stack.
ADDED:
You can find great explanation in RWH book
Here is a basic example:
import Control.Monad.Trans
import Control.Monad.Trans.Writer
import Control.Monad.Trans.Reader hiding (ask)
-- `ask` from transformers
-- ask :: Monad m => ReaderT r m r
import qualified Control.Monad.Trans.Reader as TransReader (ask)
-- `ask` from mtl
-- ask :: MonadReader r m => m r
import qualified Control.Monad.Reader as MtlReader (ask)
-- Our monad transformer stack:
-- It supports reading Int and writing String
type M m a = WriterT String (ReaderT Int m) a
-- Run our monad
runM :: Monad m => Int -> M m a -> m (a, String)
runM i action = runReaderT (runWriterT action) i
test :: Monad m => M m Int
test = do
tell "hello"
-- v <- TransReader.ask -- (I) will not compile
v1 <- lift TransReader.ask -- (II) ok
v2 <- MtlReader.ask -- (III) ok
return (v1 + v2)
main :: IO ()
main = runM 123 test >>= print
Note that (I) will be rejected by compiler (try it to see the error message!). But (II) compiles, thanks to MonadTrans ("explicit lifting"). Thanks to MonadReader, (III) works out of the box ("implicit lifting"). Please read RWH book for explanation how it works.
(In the example we import ask from two different modules, that is why we need qualified import. Usually you will use only one of them at a time.)
Also I didn't mean to specifically ask about Writer.
Not sure I understand... Reader, State and others use the same schema. Replace Writer with State and you will have an explanation for State.

Resources