Monad of No Return: Issues with `(>>) = (*>)`

Thanks for your comment, I appreciate the effort you’ve put into your research here. This is almost exactly what I was looking for by making this post.

With your hand rolled NonEmpty I can definitely see the issues you’ve presented. I tried to mitigate them by occasionally adding in a bind to the (*>) version, but it only reduced the memory by a small amount.

I concur with the others that this being brought to light is an excellent opportunity to remove this performance disparity. While I’m not sure of the direct route there, being able to ensure that switching between what should be identical operators is a completely fine and safe operation is something we should be aiming towards.
My “laziest” route is to emit a warning on a Monad instance if the Applicative instance does not define its own (*>), recommending that users define (*>) = thenM, which will no doubt be annoying but will also make sure that those that follow best practice will avoid performance issues using their monads and applicatives.
Alternatives include either instrinsic superclasses, instance templates, or something like them to make the default implementation of (*>) when there is a Monad instance be thenM.

Improving the compilation of a default (*>) implementation is obviously the best way to go, however. The most obvious case we’d find performance regressions like this is using traverse_, (which uses (*>)) versus mapM_; even if this proposal doesn’t get through, we need to fix this.

4 Likes