haskell / mtl Goto Github PK
View Code? Open in Web Editor NEWThe Monad Transformer Library
Home Page: http://www.haskell.org/haskellwiki/Monad_Transformers
License: Other
The Monad Transformer Library
Home Page: http://www.haskell.org/haskellwiki/Monad_Transformers
License: Other
Hi! My first time posting issues here, so just wanted to start by
saying thanks for maintaining MTL! It's a life saver library that I
use in practically all projects. 🙌
OK, so I'm at ZuriHac and focusing on documentation improvements in
the Haskell ecosystem. I've noticed the documentation is a little thin
and confusing in terms of "at a first sight" experience, perhaps
coming in as a Haskell novice wondering what this MTL thing might be.
This issue is meant as a starting point for further discussion and
contributions, not as a complaint. It focuses on the "first contact"
aspect and the README. I'd be happy to send PRs once we are one the
same page on what to do.
On GitHub:
The README only links to the Hackage page. The website URL points to
the MTL wiki (http://www.haskell.org/haskellwiki/Monad_Transformers).
On Hackage:
http://hackage.haskell.org/package/mtl
The package description is:
Monad classes using functional dependencies, with instances for
various monad transformers, inspired by the paper Functional
Programming with Overloading and Higher-Order Polymorphism, by Mark
P Jones, in Advanced School of Functional Programming, 1995
(http://web.cecs.pdx.edu/~mpj/pubs/springschool.html).
Looking at the hyperlinked paper, the abstract says:
[...] Focusing on practical applications rather than implementation or theo-
retical details, these notes examine a range of extensions that provide
more flexible type systems while retaining many of the properties that
have made the original Hindley/Milner system so popular. [...]
There is no link to the Wiki, the "Home page" link goes back to the GitHub repo.
Module docs on Hackage:
Many modules already have great documentation and examples in the
Haddock module documentation! 👍
The same paper which is linked in the description, is also linked in
several modules (http://web.cecs.pdx.edu/~mpj/pubs/springschool.html),
as "Inspired by the paper ...".
On the Wiki:
https://wiki.haskell.org/Monad_Transformers
The page describes not only MTL, but different Haskell implementations
of monad transformers and their history. Later, it also compares the
libraries, e.g. "Shall I use MTL or transformers?"
The links at the bottom are:
Reflection on the current state of things:
So to summarize, concentrate the overview and introductory
documentation in the README and improve it.
There are other things we could work on and link from the README:
transformers
(extract from theI hope this provides a useful starting point for working on the MTL
overview and introductory documentation, and happy to get feedback and
discussions going!
By the way, @gilligan helped me put this issue together.
Cheers!
There is a MonadError instance defined for Either, but neither Maybe nor List ([]) even though the transformer versions of those types are defined.
As it stands, users who may wish to treat a failure as a Nothing or empty list must define Orphaned instances.
More "positive" laws relating pass
and listen
to tell
are conspicuously absent in both the mtl
and transformers
. This expands upon issue #25.
We currently at best have laws stated in terms of runWriterT
which is unsatisfying.
The Monad
constraint on MonadReader
is a bit too constraining for me; I'd really like to loosen that constraint to Applicative
(or looser?) while breaking the least amount of code possible.
So now that we've made Applicative
a superclass of Monad
, that would make such a transition considerably easier. However, simply changing Monad
to Alternative
, while it shouldn't break instances, would break client code that use the Monad
class but don't explicitly declare (Monad m, MonadReader m) =>
.
It seems fairly safe to assume there's more client code than there are instances, one possibility would be to have something like the following:
class Applicative m => ApplicativeReader r m | m -> r where
ask :: ...
local :: ...
reader :: ...
class (Monad m, ApplicativeReader m) => MonadReader m
instance (Monad m, ApplicativeReader m) => MonadReader m
Then, the only thing people would have to do would be to replace the name MonadReader
with ApplicativeReader
in all their instances. This might cause some problems for people who don't update their instances and have have OverlappingInstances
turned on; it would seem preferable to make MonadReader
a closed typeclass or a typeclass synonym.
Making MonadReader
a typeclass synonym is possible with ConstraintKinds
, but would also require ConstraintKinds
to be enabled in all code that mentions the MonadReader
constraint.
Mark Lentczner (@mzero) sent in his Control.Monad.Exception, and there is also MonadCatchIO-transformers
.
Do these belong within the scope of the mtl
?
Affects:
Control.Monad.State.Strict
Control.Monad.Writer.Strict
Control.Monad.RWS.Strict
From Ben Gamari:
In general it would be nice if the notion of strictness were better
addressed in the major libraries' documentation. It's a nuanced
issue which is not fully captured by a ".Lazy" or ".Strict" in the
module name.
It seems to me that this function might make a useful addition to Control.Monad.Except:
-- | ExceptT analog to the 'try' function.
tryExceptT :: Monad m => ExceptT e m a -> ExceptT e m (Either e a)
tryExceptT = lift . runExceptT
The GHC 8.4.1 release is quickly approaching and i would like to have all of the submodules finalized by next alpha. For this we will likely need a new release.
I'm actually not sure whether this is an issue, or if it fits the project goals at all, but I keep wondering why we can't have an encapsulation of core exceptions from IO
actions (since they are far too common) under a more type-safe approach such as MonadError e m
.
What I'm thinking about is something like
import Control.Monad.Except
import Control.Exception (Exception (..), try)
liftErrorIO :: (Exception e, MonadIO m, MonadError e m) => IO a -> m a
liftErrorIO = liftIO . try >=> either throwError return
liftedBracket :: (Exception e, MonadIO m, MonadError e m) =>
m a
-> (a -> m b)
-> (a -> m c)
-> m c
liftedBracket acquire release comp = do
resource <- acquire
result <- catchError (comp resource) (\e -> release resource >> throwError e)
_ <- release resource
return result
instance Monoid w => MonadWriter w ((,) w) where
writer = swap
tell = flip (,) ()
listen ~(w, a) = (w, (a, w))
pass ~(w, ~(a, f)) = (f w, a)
It might be necessary to put in a bit of CPP as the Monad instance of Monoid w => Monad ((,) w)
only came around in base version 4.9.0.0, but to me this still seems like a worthwhile addition with no significant downsides.
Downloading a fresh copy of https://hackage.haskell.org/package/mtl-2.2.1/mtl-2.2.1.tar.gz from https://hackage.haskell.org/package/mtl gives me a mtl.cabal
file containing:
build-depends: base < 6, transformers == 0.4.*
However, the hackage page https://hackage.haskell.org/package/mtl claims transformers (>=0.4 && <0.6)
.
The latter is actually needed ─ how can these diverge?
The MonadCont
for both Lazy and Strict StateT
uses liftCallCC'
from their respective modules.
However, if you look at the documentation for these functions, they both say:
It does not satisfy the laws of a monad transformer.
(As opposed to liftCallCC
, which presumably does.)
Is the documentation for liftCallCC'
incorrect (in which case this is something that should be fixed in transformers
), or is mtl really using a function that prevents the monad transformer laws from being satisfied?
(This is also true in pre-transformers mtl, so it's highly possible that the behaviour was just copied over. The comment about not satisfying the monad transformer laws was not added until transformers-0.2.0.0
... which is also the first version of transformers used by mtl.)
I'm experimenting with mtl
master
and encountered this issue with tls
:
Network/TLS/ErrT.hs:19:35: error:
Module ‘Control.Monad.Error.Class’ does not export ‘Error(..)’
|
19 | import Control.Monad.Error.Class (Error(..))
| ^^^^^^^^^
I didn't see this mentioned in the changelog. Possibly an oversight?!
Reader.hs
documentation has an example that makes me very uneasy because it uses fromJust
. I think code snippets make a big impression on newcomers and the example code to illustrate Reader
is not anything anyone would really write because it doesn't account for errors.
lookupVar :: String -> Bindings -> Int
lookupVar name bindings = fromJust (Map.lookup name bindings)
i personally like ExceptT
pretty much so it would be nice to have it in mtl.
These two functions are present in transformers
but not mtl
:
evalCont :: Cont r r -> r
-- The result of running a CPS computation with the identity as the final continuation.evalContT :: Monad m => ContT r m r -> m r
-- The result of running a CPS computation with return as the final continuation.Should they be re-exported? If so, I will prepare a PR.
I think we should add a type equivalent to the following to transformers (and mtl):
newtype ChoiceT m a = ChoiceT { runChoiceT :: m (Maybe (ChoiceT m a)) }
This is a monad transformer, while it's well-known that the existing ListT is not.
And perhaps we should add the relevant MonadChoice class here (subject to design discussion)
Ideally this would happen in cooperation between the two libraries, hence why I'm opening it here. We would at least need all the instances to lift MonadX through ChoiceT
Bikeshedding name suggestions: ChoiceT, StreamT, NonDetT, ...
It would be a good time to push such a change because that is when the big library shuffle is planned for.
Its signature makes it look like the modify function in the State monad but more powerful. Experience has shown me that it differs, but I don't understand how.
Control.Monad.Trans.Accum was added in transformers 0.5.3.0, but currently it's slightly painful to use because it doesn't have the usual MonadState, MonadReader, etc instances yet.
Whether it should get a MonadWriter instance that lifts the operations to the inner monad or handles them itself is a question.
SelectT was also added in transformers 0.5.3.0 and similarly lacks instances.
This is release prep for the 2.3 release of mtl
.
SelectT
MonadAccum
(including documentation)MonadSelect
(including documentation)I know this isn't the latest version of mtl
, but the problem is that mtl-2.0.1.0
claims to work with base<6
which obviously isn't true, letting cabal-install
select that version of mtl
in some cases instead of trying harder to find a different install plan. The compile error is:
Configuring mtl-2.0.1.0...
Building mtl-2.0.1.0...
Preprocessing library mtl-2.0.1.0...
[ 1 of 21] Compiling Control.Monad.Writer.Class ( Control/Monad/Writer/Class.hs, dist/build/Control/Monad/Writer/Class.o )
[ 2 of 21] Compiling Control.Monad.State.Class ( Control/Monad/State/Class.hs, dist/build/Control/Monad/State/Class.o )
[ 3 of 21] Compiling Control.Monad.Reader.Class ( Control/Monad/Reader/Class.hs, dist/build/Control/Monad/Reader/Class.o )
[ 4 of 21] Compiling Control.Monad.RWS.Class ( Control/Monad/RWS/Class.hs, dist/build/Control/Monad/RWS/Class.o )
[ 5 of 21] Compiling Control.Monad.Identity ( Control/Monad/Identity.hs, dist/build/Control/Monad/Identity.o )
[ 6 of 21] Compiling Control.Monad.Error.Class ( Control/Monad/Error/Class.hs, dist/build/Control/Monad/Error/Class.o )
Control/Monad/Error/Class.hs:93:18: Not in scope: ‘catch’
Failed to install mtl-2.0.1.0
Updating documentation index /home/hvr/.cabal/share/doc/ghc-7.8.2/index.html
cabal: Error: some packages failed to install:
mtl-2.0.1.0 failed during the building phase. The exception was:
ExitFailure 1
Currently transformers
has a type called Control.Monad.Trans.Writer.CPS.WriterT
that does not have accompanying MonadWriter
/ lifting instances in MTL. These instances are currently implemented as orphan instances in writer-cps-mtl
. It should be possible to pretty much just copy over the modules defined in that package over to mtl
to gain support.
The existence of these classes is extremely strange to me. I think now that ListT and ErrorT are being removed, these can be deprecated. What do others think? @haskell/core-libraries-committee
Also, this needs a proper library mailing list proposal
IO
now has these instances in base
and it seems that most monads could/should have them now.
We'd expect local id == id
, but this isn't true for ContT
.
Let:
localTheCont :: MonadReader Int m => ContT r m ()
localTheCont = ContT $ \c -> local (+1) (c ())
test1 :: MonadReader Int m => ContT r m Int
test1 = localTheCont >> ask
test2 :: MonadReader Int m => ContT r m Int
test2 = local id localTheCont >> ask
then
runReader (evalContT test1) 1 == 2
runReader (evalContT test2) 1 == 1
Breaking apart the instance reveals what goes wrong:
local id localTheCont
= ContC $ \c ->
i <- ask
local id $ runContC localTheCont (local (const i) . c)
= ContC $ \c ->
i <- ask
local id $ local (+1) ((local (const i) . c) ())
= ContC $ \c ->
i <- ask
local (const i . (+1) . id) (c ())
= ContC $ \c ->
i <- ask
local (const i) (c ())
Removing the instance would undoubtedly cause too much breakage, but perhaps a warning in the docs is warranted.
It's possible to define a liftCallCC
for any MonadTrans
as follows:
liftCallCC :: (MonadTrans t, Monad (t m), Monad m)
=> CallCC m (t m a) b -> CallCC (t m) a b
liftCallCC callCC main = join . lift . callCC $ \exit ->
return $ main (lift . exit . return)
This satisfies the uniformity condition as long as you assume callCC
is algebraic; that is,
callCC (\exit -> f (g >=> exit) >>= g) = callCC f >>= g
Which should always be true, except perhaps for StateT
's instance of MonadCont
, since that doesn't satisfy the uniformity condition.
I've sketched a proof here.
This has been used in the wild by @ekmett in free
.
Given its usefulness for defining MonadCont
instances for novel monad transformers, I feel the generic liftCallCC
or some variant thereof should be added to mtl
(or transformers
).
Binding strategy:
The bound function is applied to the input value. Identity x >>= f == Identity (f x)
I think it should be Identity x >>= f == f x.
For the sake of completeness, should Control.Monad.Identity
re-export IdentityT
from Control.Monad.Trans.Identity
from transformers?
Why there is no MaybeT transformer in library? There is a implemenation of it here:
http://www.haskell.org/haskellwiki/New_monads/MaybeT
Why not to put it inside?
Currently there is no relation between MonadFail and MonadError. And this is bad.
What is the right way to report errors if you know that they are strings and you want to be able to catch them in pure code? Currently we have such options: Either String, MonadFail and MonadError. Either String is not very general, so we have MonadFail and MonadError. Unfortunately, they are incompatible in both ways! What to do? Well, I propose creating instance instance (MonadError String a) => MonadFail a
https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html has a heading "The ErrorT monad transformer" that describes ExceptT.
From a quick look, the following classes would benefit from MINIMAL pragmas since they have recursive method definitions:
Maybe some others that I missed.
Hi,
I made the packages writer-cps-mtl and writer-transformers-mtl which implement a stricter writer monad transformer in continuation passing style as was discussed before on the Haskell mailing list etc
See also the current reddit discussion:
https://www.reddit.com/r/haskell/comments/50hf2s/stricter_writert_and_rwst_replacements/
Would you be interested in including this into mtl? If not it can also exist as a separate package, but I guess it would be nice to have a well-behaving writer around in the standard toolbox.
Daniel
hello, everyone.
I found ContT a bit weird.
newtype ContT r m a = ContT { runContT :: (a -> m r) -> m r }
Why can't we define it like this?
newtype ContT r m a = ContT {runContT :: (m a -> m r) -> m r}
evalContT :: (Monad m) => (m a -> m r) -> ContT r m a -> m r
evalContT f m = runContT m f
instance Functor m => Functor (ContT r m) where
fmap f m = ContT $ \c -> runContT m (c . fmap f)
instance Applicative m => Applicative (ContT r m) where
pure x = ContT ($ pure x)
f <*> v = ContT $ \c -> runContT f $ \g -> runContT v (\tmp -> c (g <*> tmp))
-- never del this
-- res <- runContT <$> (k <$> x)
-- res c
instance (Monad m) => Monad (ContT r m) where
m >>= k = ContT $ \c -> runContT m ((<$>) k >=> (($ c) . runContT))
instance MonadTrans (ContT r) where
lift m = ContT $ \g -> g m
instance MonadIO m => MonadIO (ContT r m) where
liftIO io = ContT $ \c -> c (liftIO io)
https://github.com/EMQ-YangM/fused-effects-cont/blob/5fd9dc23b3640a9702acacdf55f450dfb6c719db/src/Cont.hs#L24-L52
It seems more normal to look at it this way.
I hope I can use ContT in fused-effects.
If I have any mistakes, please let me know.
Is there a technical reason why this instance doesn't exist? It seems well-behaved (some sanity-check proofs are at the bottom of the page).
fstP :: Product f g a -> f a
fstP (Pair a _) = a
sndP :: Product f g a -> g a
sndP (Pair _ a) = a
instance (MonadError e f, MonadError e g) => MonadError e (Product f g) where
throwError e = Pair (throwError e) (throwError e)
catchError (Pair ma mb) f = Pair (catchError ma (fstP . f)) (catchError mb (sndP . f))
catchError m throwError = m
catchError (Pair f g) throwError
= { inline catchError }
Pair (catchError f (fstP . throwError)) (catchError g (sndP . throwError))
= { inline throwError }
Pair
(catchError f (fstP . (\e -> Pair (throwError e) (throwError e)))
(catchError g (sndP . (\e -> Pair (throwError e) (throwError e)))
= { inline (.) }
Pair
(catchError f (\e -> fstP (Pair (throwError e) (throwError e)))
(catchError g (\e -> sndP (Pair (throwError e) (throwError e)))
= { eta-reduction }
Pair
(catchError f (\e -> throwError e))
(catchError g (\e -> throwError e))
= { eta-reduction }
Pair
(catchError f throwError)
(catchError g throwError)
= { hypothesis }
Pair f g
catchError (throwError e) pure = pure e
catchError (throwError e) pure
= { inline throwError }
catchError (Pair (throwError e) (throwError e)) pure = pure e
= { inline catchError }
Pair (catchError (throwError e) (fstP . pure)) (catchError (throwError e) (sndP . pure))
= { inline pure, (.) }
Pair
(catchError (throwError e) (\x -> fstP (Pair (pure x) (pure x))))
(catchError (throwError e) (\x -> sndP (Pair (pure x) (pure x))))
= { eta-reduction }
Pair
(catchError (throwError e) (\x -> pure x))
(catchError (throwError e) (\x -> pure x))
= { eta-reduction }
Pair
(catchError (throwError e) pure)
(catchError (throwError e) pure)
= { hypothesis }
Pair (pure e) (pure e}
= { pure }
pure e
Control.Monad.State.Class.modify'
is defined as follows.
modify' :: MonadState s m => (s -> s) -> m ()
modify' f = state (\s -> let s' = f s in s' `seq` ((), s'))
According to the instance of MonadState s (StateT s m)
, this is equivalent to:
modify' f = state (\s -> let s' = f s in s' `seq` ((), s'))
= StateT (return . (\s -> let s' = f s in s' `seq` ((), s')))
= StateT (\s -> return (let s' = f s in s' `seq` ((), s')))
Because (let s' = f s in s' `seq` ((), s'))
is encapsulated by return
,
f s
does not evaluated when modify f
is evaluated.
I think this behavior is NOT desired.
In the transformers
package, modify'
is defined as follows.
modify' :: (Monad m) => (s -> s) -> StateT s m ()
modify' f = do
s <- get
put $! f s
With this definition, modify' f
evaluates f s
, as expected.
It seems that pull request #13 fixes this issue, but it haven't been merged for a few years.
I notice that instead of requiring MonadState s m
, it's possible to require MonadReader s m
:
instance MonadReader s m => MonadState s (ContT r m) where
get = ContT (\smr -> ask >>= smr)
put s = ContT (\umr -> local (\_ -> s) (umr ()))
Is this preferable?
swagger2
makes use of a DeclareT
monad transformer which lies between the WriterT
and StateT
.
It also provides an mtl
-style MonadDeclare
type class with some laws.
Since the construction is not specific to swagger2
, it might make sense to add it to mtl
and transformers
.
A corresponding issue for transformers
is here.
The rationale for Declare
is generating a list of Swagger schema definitions.
To avoid duplicates and to fix recursive schemas we need to look
at the previous definitions.
But we don't want a full State
because we don't want someone to accidentally erase/change all/some previous definitions in a ToSchema
instance for a user data type.
The laziness plays nice part here and allows (mutually) recursive schemas to be written easily (e.g. see sources for declareSchemaRef
).
Yes, there's a warning on transformers, but it should be re-warned here.
Many instances for MonadError have Error constraints that are completely unused. In particular, the MonadError instance for Either does not use its Error constraint. This does nothing except restrict generality.
They are finally removed after several years of deprecation (https://hub.darcs.net/ross/transformers/patch/7c809b6f9db019ed761c971d10c86fa004f60d89). Is it OK to simply remove them from mtl
as well? If so I can submit a PR.
https://hackage.haskell.org/package/transformers-0.6.0.0/changelog
Could you upload a compatible release or make a Hackage revision?
The laws for MonadState
, etc. are missing from the haddocks.
It may be a good idea to add them.
I've had programs that repeatedly modified state leak memory because modify does not force the f s thunk.
This is surprising in a library with "strict" in its name..
The documentation for MonadError says "In these cases you will have to explicitly define instances of the Error and/or MonadError classes."
Error
links to Control.Monad.Error.Class. If you try to import that in a Haskell module, you get deprecation warnings:
MyModules.hs:208:14: Warning:
In the use of type constructor or class ‘Error’
(imported from Control.Monad.Error.Class, but defined in transformers-0.4.1.0:Control.Monad.Trans.Error):
Deprecated: "Use Control.Monad.Trans.Except instead"
But Control.Monad.Trans.Except does not export an Error
class.
mtl
now provides a number of useful, unreleased changes (among them instances for Control.Monad.Trans.Writer.CPS
and Control.Monad.RWS.CPS
which were introduced in transformers-0.5.6.2
).
Can we get a new release of this library in time for GHC 8.10, or has that ship sailed?
This is subtle issue. But when you do
import Prelude ()
import Prelude.Compat --base-compat-0.11
import Control.Monad.Reader -- mtl-2.2.2
Then the fail
is ambiguous.
it's my own fault, that I imported Control.Monad.Reader
unqualified, but still this is surprising and nasty.
The documentation cited in the title contains the line "Identity x >>= f == Identity (f x)". However, if I am not mistaken, this is wrong: it should read "Identity x >>= f == f x".
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.