Re: Monad of no `return` Proposal (MRP): Moving `return` out of `Monad`

Hi,
In Agda, we added support for GHC 7.10.* following the migration guide
available at
https://ghc.haskell.org/trac/ghc/wiki/Migration/7.10
In particular, we added instances of Applicative using
instance Applicative Foo where
pure = return
(<*>) = ap -- defined in Control.Monad
After reading the "Monad of no `return` proposal" below, I have two questions:
1. Shouldn't the above migration guide to be changed to something like
instance Applicative Foo where
pure = <copy-the-definition-of-return>
...
instance Monad Foo where
return = pure
?
2. Since in Agda code `return` is used everywhere, does follow (1) has
some performance cost?
All the best,
On 24 September 2015 at 16:43, Herbert Valerio Riedel
Hello *,
Concluding AMP and MFP, We (David and I) proudly present you the final installment of the Monad trilogy:
Monad of no `return` Proposal =============================
TLDR: To complete the AMP, turn `Monad(return)` method into a top-level binding aliasing `Applicative(pure)`.
Current Situation -----------------
With the implementation of Functor-Applicative-Monad Proposal (AMP)[1] and (at some point) the MonadFail proposal (MFP)[2] the AMP class hierarchy becomes
class Functor f where fmap :: (a -> b) -> f a -> f b
class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b u *> v = …
(<*) :: f a -> f b -> f a u <* v = …
class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a return = pure
(>>) :: m a -> m b -> m b m >> k = …
class Monad m => MonadFail m where fail :: String -> m a
Consequently, the `Monad` class is left with a now redundant `return` method as a historic artifact, as there's no compelling reason to have `pure` and `return` implemented differently.
Traditionally, `return` is often used where `pure` would suffice today, forcing a `Monad` constraint even if a weaker `Applicative` would have sufficed.
As a result, language extensions like `ApplicativeDo`[3] have to rewrite `return` to weaken its `Monad m =>` constraint to `Applicative m =>` in order to benefit existing code at the cost of introducing magic behavior at the type level.
Finally, this redundancy becomes even more significant when viewed in light of the renewed Haskell standardisation process[7]: The next Haskell Report will almost certainly incorporate the AMP (and MFP) changes, and there's no justification for the Report to retain `return` as a method of `Monad`. A good reason would have been to retain backward compatibility with Haskell 2010. However, as the AMP superclass hierarchy requires `Monad` instances to be accompanied by `Applicative` instances (which aren't part of Haskell 2010, c.f. [6]), backward compatibility with Haskell 2010 goes out the window when it comes to defining `Monad` instances (unless via use of `-XCPP` or similar). Consequently, meeting the high bar for a formal document such as the Haskell Report demands that `Monad` shall not carry a redundant `return` method that serves no purpose anymore. Moreover, getting `return` out of the way is desirable to facilitate standardising potential candidates such as the earlier mentioned `ApplicativeDo` in the future and avoids the technical debt incurred by keeping around this language wart.
Proposed Change ---------------
Remove `return` as a method from the `Monad` class and in its place define a top-level binding with the weaker `Applicative` typeclass constraint:
-- | Legacy alias for 'pure' return :: Applicative f => a -> f a return = pure
This allows existing code using `return` to benefit from a weaker typeclass constraint as well as cleaning the `Monad` class from a redundant method in the post-AMP world.
A possible migration strategy is described further below.
Compatibility Considerations ----------------------------
Generalizing the type signature of a function from a `Monad` constraint to its superclass `Applicative` doesn't cause new type-errors in existing code.
However, moving a method to a top-level binding obviously breaks code that assumes `return` to be a class method. Foremost, code that defines `Monad` instances it at risk:
### Instance Definitions
Code defining `return` as part of an instance definition breaks. However, we had the foresight to provide a default implementation in `base-4.8` for `return` so that the following represents a proper minimal instance definition post-AMP:
instance Functor Foo where fmap g foo = …
instance Applicative Foo where pure x = … a1 <*> a2 = …
instance Monad Foo where m >>= f = …
-- NB: No mention of `return`
Consequently, it is possible to write forward-compatible instances that are valid under this proposal starting with GHC 7.10/`base-4.8`.
Heuristically `grep`ing through Hackage source-code reveals a non-negligible number of packages defining `Monad` instances with explicit `return` definitions[4]. This has a comparable impact to the AMP, and similarly will require a transition scheme aided by compiler warnings.
### Module Import/Export Specifications
A second source of incompatibility may be due to `import`s. Specifically module import that assert `return` to be a method of `Monad`, e.g.:
import Control.Monad (Monad ((>>=), return))
or
import Prelude hiding (Monad(..)) import Control.Monad (Monad(..)) as Monad
f = Monad.return ()
The dual situation can occur when re-exporting `return` via module export specifications.
However, given that `return` is exported by `Prelude` and the examples above are rather artificial, we don't expect this to be a major source of breakage in the case of `return`. In fact, a heuristic grep[5] over Hackage source-code revealed only 21 packages affected.
### Example for writing compatible code
instance Functor Foo where fmap g foo = …
instance Applicative Foo where pure x = … a1 <*> a2 = …
instance Monad Foo where m >>= f = …
#if !(MIN_VERSION_base(4,8,0)) return = pure #endif
Migration Strategy ------------------
The migration strategy is straightforward:
**Phase 1** *(GHC 8.0)*: Implement new warning in GHC which gets triggered when `Monad` instances explicitly override the default `return` method implementation.
**Phase 2** *(GHC 8.2 or later)*: When we're confident that the majority of Hackage has reacted to the warning (with the help of Stackage actively pursuing maintainers to update their packages) we turn the `return` method into a top-level binding and remove the warning implemented in Phase 1 from GHC again.
Discussion period -----------------
A discussion period of three weeks (until 2015-10-15) should be enough to allow everyone to chime in as well as leave enough time to make the required preparations for GHC 8.0 should this proposal pass as we hope.
----
[1]: https://wiki.haskell.org/Functor-Applicative-Monad_Proposal [2]: https://wiki.haskell.org/MonadFail_Proposal [3]: https://ghc.haskell.org/trac/ghc/wiki/ApplicativeDo [4]: https://gist.github.com/hvr/b0e34463d85b58f169d9 [5]: https://gist.github.com/hvr/afcd040783d980594883 [6]: https://ghc.haskell.org/trac/ghc/ticket/9590 [7]: https://mail.haskell.org/pipermail/haskell-prime/2015-September/003936.html
--
_______________________________________________ Libraries mailing list Libraries@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
-- Andrés

Hello, On 2015-10-03 at 16:43:53 +0200, Andrés Sicard-Ramírez wrote:
In Agda, we added support for GHC 7.10.* following the migration guide available at
https://ghc.haskell.org/trac/ghc/wiki/Migration/7.10
In particular, we added instances of Applicative using
instance Applicative Foo where pure = return (<*>) = ap -- defined in Control.Monad
After reading the "Monad of no `return` proposal" below, I have two questions:
1. Shouldn't the above migration guide to be changed to something like
instance Applicative Foo where pure = <copy-the-definition-of-return> ...
instance Monad Foo where return = pure
?
Indeed, I've updated the GHC wiki page accordingly, thanks for pointing that out!
2. Since in Agda code `return` is used everywhere, does follow (1) has some performance cost?
If you have generic code which doesn't get specialised to a specific class dictionary for Monad/Applicative then there's the overhead of an additional indirection (depending on which way around the Monad/Applicative instances were defined). The AMP instructions were formulated in a way to make manual refactoring easiest, while they don't necessarily match how you'd define instances from scratch in a post-AMP setting. Given the convenience of the post-AMP default-method-implementation of `return`, I expect new code to start defining `return` in terms of `pure` rather than the other way around, and older existing code will probably slowly migrate to that state as well. Given the usual 3-major-GHC-version compatibility window convention we strive for in Hackage-released Haskell code, starting with GHC 8.2, code written not defining `return` explicitly anymore will be compliant with this convention. In any case, if you *know* that you incur an overhead if you use `return` rather than `pure` in your code (due to e.g. non-specialisation) and don't want to refactor your code just yet to use `pure` instead of `return`, you can hide the import of `return` and define an inlineable `return = pure` top-level binding in its place. Otoh, if you have code which doesn't specialise the Monad-instances I'd expect the `return`-induced overhead to be a minor contribution as GHC will have already missed a few other opportunities to inline and optimise your code. Cheers, hvr
participants (2)
-
Andrés Sicard-Ramírez
-
Herbert Valerio Riedel