
for is in rather broad use; I know you have a pet alternative definition. ;)
sequenceA is unlikely to be reclaimed any time soon -- it is in the
Traversable class.
There are is also a couple of annoying side-cases where mapM/sequence can
be implemented with better stack behavior than traverse/sequenceA.
Basically if you have a very _large_ but not infinite list like container
you can find a way to fold it that abuses your heap more when using the
Monad to dump into an accumulator and reverse, rather than the stack which
you wind up on in the Applicative case.
When you add the fact that many folks have manual mapM implementations
already, we can't remove mapM/sequence from the class or even generalize
their signatures *at this time*.
We can and have at least simplified their default definitions so they take
advantage of the Applicative superclass rather than go through the
WrappedMonad overhead. This means that most consumers of mapM will just get
automatically upgraded behavior even if the types don't change.
We can decide over the next year if in 7.12 we want to deprecate manual
definitions of mapM, if we can figure out a workaround for the the
aforementioned counter-example, or find that it isn't an issue in practice,
but it isn't something that can reasonably happen now.
The AMP and Foldable/Traversable work are starting to let a lot of things
work together, but while reclaiming some names some day might be nice, it
is something that is pretty much off the table in the short term.
Longer term we can talk about deprecation/consolidation of more
combinators, but I'd really like to push such discussions out past 7.12 to
a time when folks have had time to adapt to the status quo and start to
find the redundancy more annoying than useful, and when you are more likely
to get something like what you'd want without incurring undue pain.
That'd need to be a much broader discussion though.
*tl;dr* It's complicated.
-Edward
On Sat, Nov 8, 2014 at 3:39 PM, Andreas Abel
Thanks for your replies.
My hope for AMP was to get generalization of effectful combinators without requiring more identifiers (and actually freeing some, like making `sequenceA` and `for` obsolete). I see that it is not so easy if GHC's reduction behavior has to be taken into account.
So +1 from me if you deem liftA4 and liftA5 necessary.
On 08.11.2014 21:21, David Feuer wrote:
On Sat, Nov 8, 2014 at 2:39 PM, Edward Kmett
mailto:ekmett@gmail.com> wrote: We have two competing tensions that have been guiding the work so far, which is scattered across a few dozen different proposals and patches in Phab and is alarmingly intricate to detail.
We've generally been guided by the notion you suggest here. In the wake of the AMP, the 'M' suffix really comes to mean the minimal set of effects needed to get the effect. This lets us generalize large numbers of combinators in Control.Monad (e.g. when/unless/guard) to 'just work' in more contexts.
However, we also have to balance this carefully against two other concerns:
1.) Not breaking user code silently in undetectable ways.
This is of course the utmost priority. It guides much of the AMP, including the 'backwards' direction of most of the dependencies. e.g. The reality is a large number of folks wrote (*>) = (>>) in their code, so e.g. if we defined (>>) = (*>), we'd get a large chunk of existing production code that just turns into infinite loops. We can always do more later as we find it is safe, but "first do no harm."
Indeed. I've looked at quite a few Applicative and Monad instances lately, and one conclusion I've come to is that it often makes *more* sense to define (*>) = (>>) than the other way around. In particular, monads like IO and ST have a (>>=) that's about as simple as anything remotely interesting you can do with them. In particular,
fs <*> xs = fs >>= \f -> xs >>= \x -> return (f x)
is about as simple as it gets. The default definition of (*>) looks like this:
m *> n = (id <$ m) <*> n
But these don't have particularly special Functor instances either. So this expands out to
m *> n = fmap (const id) m <*> n
which becomes
m *> n = (m >>= (return . const id)) >>= \f -> n >>= \x -> return (f x)
Can we say "ouch"? We now have to hope that GHC inlines enough to do anything more. If it does, it will take a few extra steps along the way.
Compare this mess to (>>):
m >> n = m >>= \_ -> n
So I think there's a pretty clear case for (*>) = (>>) actually being the right thing in a lot of cases.
2.) Not introducing rampant performance regressions.
David Feuer has been spending untold hours on this, and his work there is a large part of the source of endless waves of proposals he's been putting forth.
Considering `liftM2` as 'redundant' from `liftA2` can be dangerous on this front.
That's definitely a valid concern, for the reasons described above. Everything works out nicely because of monad laws, but GHC doesn't know that.
The decision of if we can alias liftM3 to liftA3 needs to be at least /partially/ guided by the question of whether the latter is a viable replacement in practice. I'm not prepared to come down on one side of that debate without more profiling data.
Yes, that makes sense. I think the problem fundamentally remains the same--the monadic operations ultimately need to be inlined and completely twisted around in order to be fast.
Adding liftA4, liftA5 runs afoul of neither of these caveats. Aliasing liftMn to liftAn is something that /potentially/ runs afoul of the latter, and is something that we don't have to do in a frantic rush. The world won't end if we play it safe and don't get to it in time for 7.10.
The more I think about it, the more right I think you are.
David
-- Andreas Abel <>< Du bist der geliebte Mensch.
Department of Computer Science and Engineering Chalmers and Gothenburg University, Sweden
andreas.abel@gu.se http://www2.tcs.ifi.lmu.de/~abel/