# Monad of No Return: Next Steps

**URL:** <https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443>\
**Category:** Announcements\
**Created:** [February 19, 2025, 3:02pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443 "2025-02-19T15:02:38Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![L0neGamer](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/l0negamer/32/4536_2.png) [@L0neGamer](https://discourse.haskell.org/u/L0neGamer)\
**Post date:** [February 19, 2025, 3:02pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/1 "2025-02-19T15:02:38Z")

</div>

The proposal [monad of no return](https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/monad-of-no-return) has been in the process of being implemented for a while. I’m collecting community feedback on the proposal (and specifically phase 3) in the hopes that as a community we can move forwards in making Haskell a better language. I encourage people to read the proposal above, but I’ll summarise and quote below where relevant.

# What is this proposal?

The proposal outlines that with making `Applicative` a superclass of `Monad` (which happened with [Functor-Applicative-Monad](https://wiki.haskell.org/Functor-Applicative-Monad_Proposal) proposal pre-2015), the definitions of `return` and `(>>)` in the `Monad` typeclass are redundant, as they can always be lawfully expressed in terms of `pure` and `(*>)` respectively. As such, they can and (by this proposal) should be removed from the typeclass, and defined in terms of their `Applicative` equivalents.

# Why should we do this?

Having `return` and `(>>)` introduces redundancy, redundancy that

> _violates the “making illegal states unrepresentable” idiom: Due to the default implementation of return this redundancy leads to error-prone situations which aren’t caught by the compiler; for instance, when `return` is removed while the `Applicative` instance is left with a `pure = return` definition, this leads to a cyclic definition which can be quite tedious to debug as it only manifests at runtime by a hanging process._ [[1]](https://gitlab.haskell.org/ghc/ghc/-/wikis/Proposal/Monad-Of-No-Return#current-situation)

Additionally, keeping `return` and `(>>)` as `Monad` methods means that in cases where a weaker `Applicative` constraint would suffice we instead get a `Monad` constraint.

Currently the status quo is to optimise `(>>)` over `(*>)`, which means that there are _unexpected performance regressions_[[1]](https://gitlab.haskell.org/ghc/ghc/-/wikis/Proposal/Monad-Of-No-Return#current-situation) when generalising code; this is particularly noteworthy in the case of `Foldable`/`Traversable` methods, and additionally blocks the collation of their methods (for example `mapM` vs `traverse`) too.

A minor benefit would be to reduce the sizes of the class dictionaries, as well as avoid additional indirection through `Monad` when `Applicative` will suffice.

# What’s been done so far?

So far we’ve completed phase 1 of the [migration strategy](https://gitlab.haskell.org/ghc/ghc/-/wikis/Proposal/Monad-Of-No-Return#reduced-breakage-variant). Phase 1 introduced a warning when non-lawful instances of `return` and `(>>)` are defined. This was implemented in GHC 8.0, and added to the default warning set in GHC 9.2.

# What’s next?

Phase 2 moves `return` and `(>>)` to be top level definitions, and lets GHC ignore lawful definitions of `return` and `(>>)` in `Monad`. As part of this, non-lawful definitions would result in a compile time error, while lawful definitions are left alone.

Phase 3 would then start warning about lawful implementations, similar to phase 1’s warning on unlawful implementations.

Phase 4 would finish off the proposal by removing GHC support for `return` and `(>>)` overrides, turning any such override into a compile time error.

# So why are you posting about this?

It’s been a while since there’s been much update on this front, which is a shame for a proposal that would make Haskell a cleaner and more coherent language to program in. For many, the move to phase 2 won’t impact them, as unlawful definitions have been warned against for a time. Phase 3 has the potential to impact more people, so that is the primary focus of this post.

## Why is phase 3 a sticking point?

It isn’t! What is currently lacking is community input in order to make the changes that phase 3, and this proposal, represents digestible and easily understood by the majority of Haskell users. We’re in no rush to break setups or have confused beginners ask why their tutorials are failing them.

## What are we meant to do about this?

Getting feedback from the community is the primary purpose of this post, but I’m also taking the opportunity to ask for volunteers to split the work and make progress on this issue. If you want to take part, please let people know! GHC is a community project after all.

---

<div class="post-metadata">

**Author:** ![L0neGamer](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/l0negamer/32/4536_2.png) [@L0neGamer](https://discourse.haskell.org/u/L0neGamer)\
**Post date:** [February 19, 2025, 3:35pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/3 "2025-02-19T15:35:25Z")

</div>

`return` will likely be staying forever; at the end of this proposal, it will probably be a top level definition of `return = pure`.

Adding more keywords, or making return a keyword is drastically outside of the scope of this proposal. If you want to make that happen, feel free to make your own proposal to GHC.

---

<div class="post-metadata">

**Author:** ![rhendric](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rhendric/32/2689_2.png) [@rhendric](https://discourse.haskell.org/u/rhendric)\
**Post date:** [February 19, 2025, 7:49pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/4 "2025-02-19T19:49:10Z")

</div>

Why not skip phase 2 (i.e., do phases 2 and 3 together)?

Once you’re using a GHC that is far enough along this sequence that the in-`Monad` definitions are optional, there’s no reason to include them other than maintaining non-`CPP` compatibility with older GHCs. The only difference between phases 2 and 3 is suppressing a warning about this. But what good does that do? Suppressing the warning only delays the point at which the ecosystem transitions away from `return`-ful `Monad`. It doesn’t make the breaking change between phases 3 and 4 any less breaking. We have to eat that cost eventually, if we want this plan to succeed at all. Why not start nudging people toward the transition as soon as the alternative is made available?

---

<div class="post-metadata">

**Author:** ![L0neGamer](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/l0negamer/32/4536_2.png) [@L0neGamer](https://discourse.haskell.org/u/L0neGamer)\
**Post date:** [February 20, 2025, 7:16am UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/5 "2025-02-20T07:16:28Z")

</div>

I think I agree personally, but this revised proposal migration scheme was used to mete out warnings and breakage over a longer period of time. If more people speak up here (or like your comment) it would be worth combining phases 2 and 3 (pending CLC approval as well of course).

---

<div class="post-metadata">

**Author:** ![Kleidukos](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/kleidukos/32/1213_2.png) [@Kleidukos](https://discourse.haskell.org/u/Kleidukos)\
**Post date:** [February 20, 2025, 11:42am UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/6 "2025-02-20T11:42:44Z")

</div>

I’d be happy to give a hand on some tickets 🙂

---

<div class="post-metadata">

**Author:** ![TeofilC](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/teofilc/32/3987_2.png) [@TeofilC](https://discourse.haskell.org/u/TeofilC)\
**Post date:** [February 20, 2025, 11:56am UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/7 "2025-02-20T11:56:26Z")

</div>

I wonder if we could make “Phase 4” be a language extension that gets added to a future GHCXXX set. I’m not sure if this is feasible, but it would be very nice in terms of a smooth transition, since it would allow old lawful code to compile indefinitely. Ofc it also comes with a maintenance cost for GHC devs. I guess it depends on how much code uses this stuff and how much of it is still maintained.

---

<div class="post-metadata">

**Author:** ![L0neGamer](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/l0negamer/32/4536_2.png) [@L0neGamer](https://discourse.haskell.org/u/L0neGamer)\
**Post date:** [February 20, 2025, 12:36pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/8 "2025-02-20T12:36:45Z")

</div>

So “no extensions” GHC would allow the overrides, but “default extensions” GHC would remove support for them?  
While the idea for backwards compatibility is attractive I dislike the idea of having to support these warnings and overrides into the future; phase 4 as presented would hopefully clean up the remaining gubbins.  
Really, we should look into current packages and see how many of them define `return` or `(>>)`, which would be good to do now or as phase 2/3 implementation begins.

---

<div class="post-metadata">

**Author:** ![TeofilC](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/teofilc/32/3987_2.png) [@TeofilC](https://discourse.haskell.org/u/TeofilC)\
**Post date:** [February 20, 2025, 1:52pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/9 "2025-02-20T13:52:13Z")

</div>

> [@L0neGamer](#):
>
> So “no extensions” GHC would allow the overrides, but “default extensions” GHC would remove support for them?

Yeah that’s right. If you specify a recent enough language edition then it would not be allowed.

> [@L0neGamer](#):
>
> Really, we should look into current packages and see how many of them define `return` or `(>>)`, which would be good to do now or as phase 2/3 implementation begins.

I just grepped for `^\s*return\s*=` on Hackage. I think that will give us a pretty accurate idea of how many packages would need to be changed. That gives me 1006 packages: [Packages that contain "return =" · GitHub](https://gist.github.com/TeofilC/db6c11ae4e2cdad8be28f77622a008a4)

Grepping for `^\s*return = pure` got me 452: [Packages that contain "return = pure" · GitHub](https://gist.github.com/TeofilC/5270f357b7240015ca5e5d6b6f18a35f)

(I used [GitHub - nh2/hackage-download: Script to download all of Hackage](https://github.com/nh2/hackage-download) and `rg`)

---

<div class="post-metadata">

**Author:** ![Vlix](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/vlix/32/1551_2.png) [@Vlix](https://discourse.haskell.org/u/Vlix)\
**Post date:** [February 20, 2025, 3:01pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/10 "2025-02-20T15:01:56Z")

</div>

Could you make a list with the following match?  
`^\s*return\s*(\S+\s*)?=(?!\s*pure\s*)`

Since a package could have more than one Monad instance (e.g. for different types) and a package having at least one `return = pure` doesn’t mean it has no `return` definitions with something else, right?

---

<div class="post-metadata">

**Author:** ![TeofilC](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/teofilc/32/3987_2.png) [@TeofilC](https://discourse.haskell.org/u/TeofilC)\
**Post date:** [February 20, 2025, 3:44pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/11 "2025-02-20T15:44:12Z")

</div>

That seems to catch things like `returningClause =` as well. Also I’m not sure how best to collate the results.

Perhaps you could run the search yourself. You can download all of Hackage using this script: [GitHub - nh2/hackage-download: Script to download all of Hackage](https://github.com/nh2/hackage-download)

---

<div class="post-metadata">

**Author:** ![L0neGamer](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/l0negamer/32/4536_2.png) [@L0neGamer](https://discourse.haskell.org/u/L0neGamer)\
**Post date:** [February 20, 2025, 4:08pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/12 "2025-02-20T16:08:02Z")

</div>

Speedy research! I’ll see if I can replicate such research later to see what the common themes are of packages. If the majority of the 452 packages are small, old, or a “leaf” package (no one really depends on it) I think that would likely be fine.

Having downloaded hackage with `hackage-download`, I’ve used `rg '^\s+return\s*=' -l | sed 's|\([^/]+\)*/.*$|\1|g' | sort -u | wc -l` to count the packages.

More interesting to me is that 431 packages still define `pure = return`

---

<div class="post-metadata">

**Author:** ![Vlix](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/vlix/32/1551_2.png) [@Vlix](https://discourse.haskell.org/u/Vlix)\
**Post date:** [February 20, 2025, 4:31pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/13 "2025-02-20T16:31:09Z")

</div>

Ah, right, should have been  
`^\s*return(\s+\S+)?\s*=(?!\s*pure\s*)`

---

<div class="post-metadata">

**Author:** ![L0neGamer](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/l0negamer/32/4536_2.png) [@L0neGamer](https://discourse.haskell.org/u/L0neGamer)\
**Post date:** [February 20, 2025, 4:47pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/14 "2025-02-20T16:47:41Z")

</div>

the `\S` is doing odd things; removing it (and ending up with the command `rg '^\s*return(\s+)?\s*=(?!\s*pure\s*)' -l --pcre2 | sed 's|\([^/]+\)*/.*$|\1|g' | sort -u | wc -l`) gives 565 for me.

---

<div class="post-metadata">

**Author:** ![Vlix](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/vlix/32/1551_2.png) [@Vlix](https://discourse.haskell.org/u/Vlix)\
**Post date:** [February 20, 2025, 4:53pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/15 "2025-02-20T16:53:20Z")

</div>

I was trying to also have it match on `return x = ...` where `...` doesn’t start with `pure`. (EDIT: I think I know why now… because `\S` also matches on `=`… so maybe `[^\s=]` would work? 🤔 )  
But the 565 hits already shows that some of the 452 packages with `return = pure` also have `return` definitions that are not `return = pure`.

1006 - 452 = 554 packages that define `return =`, but do not define `return = pure`  
565 packages define a `return = (something other than pure)`

---

<div class="post-metadata">

**Author:** ![treblacy](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/treblacy/32/1782_2.png) [@treblacy](https://discourse.haskell.org/u/treblacy)\
**Post date:** [February 20, 2025, 6:56pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/16 "2025-02-20T18:56:29Z")

</div>

Because I teach Haskell in courses, I have incentive to dislike “return” because it takes extra effort (both me and students) to note that it does not mean control flow unlike all other languages.

Actually “pure” is only a less worse name. IMO “fromPureToApplicative” or “toApplicativeFromPure” would be much better in terms of self-documenting names, but who else is going to agree? And the ship has sailed anyway.

P.S. I don’t have any objection to the phases and the pace.

---

<div class="post-metadata">

**Author:** ![rhendric](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rhendric/32/2689_2.png) [@rhendric](https://discourse.haskell.org/u/rhendric)\
**Post date:** [February 20, 2025, 11:19pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/17 "2025-02-20T23:19:26Z")

</div>

Another question: other than `Monad`’s documentation (which I’m sure could be annotated in a way that makes the situation clear), is there any user-visible difference between:

- current plan for phase 2:
  - move `return` and `(>>)` to top-level
  - deprecate `-Wnoncanonical-monad-instances`, which is now a no-op
  - add new logic to GHC to ignore any canonical but redundant definitions

- a modified phase 2:
  - leave `return` and `(>>)` where they are (defer their relocation to phase 4)
  - change the current `-Wnoncanonical-monad-instances` code to raise an error instead of a warning
  - make the `-Wnoncanonical-monad-instances` flag a no-op and deprecate it

As I see it, both approaches newly forbid non-canonical definitions and still allow canonical definitions. The implementation cost seems lower in the second approach, since the existing infrastructure for the warnings is repurposed instead of having to add the concept of a class member that can be defined even if it doesn’t appear in the class. It is also a closer match to what is happening in the [semigroup-monoid](https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/semigroup-monoid) proposal, which is very similar and should probably be implemented in parallel—that proposal doesn’t move `mappend` out of `Monoid` until phase 4.

---

<div class="post-metadata">

**Author:** ![f-a](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/f-a/32/2740_2.png) [@f-a](https://discourse.haskell.org/u/f-a)\
**Post date:** [February 20, 2025, 10:38am UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/18 "2025-02-20T10:38:24Z")

</div>

Thanks for engaging with the community.

The only developers who I suspect might be miffed by this are industrial users  
with deployment on a large number of machines (for sure I recall seeing some  
complaints by such users in `haskell-cafe` with regard to the  
Applicative-Monad proposal).

I guess small steps and a clear timeline were introduced to be more  
palatable to them.

---

<div class="post-metadata">

**Author:** ![L0neGamer](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/l0negamer/32/4536_2.png) [@L0neGamer](https://discourse.haskell.org/u/L0neGamer)\
**Post date:** [February 21, 2025, 10:20am UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/19 "2025-02-21T10:20:12Z")

</div>

I think I like this approach, along with your previous suggestion to merge phases 2 and 3. When this post is about a week old I’ll ask the CLC if we can amend the proposal for this more streamlined timeline.

This would mean that the new phase 2 would:

- make noncanonical implementations of `return` and `(>>)` an error
- make canonical implementations a warning

And the new phase 3 would:

- move `return` and `(>>)` to the top level
  - by default this will make definitions of them a compile time error

- optionally: investigate Teo’s suggestion

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [February 21, 2025, 12:03pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/20 "2025-02-21T12:03:52Z")

</div>

> The only developers who I suspect might be miffed by this are industrial users

Sadly not. Also miffed will be any inexperienced user who comes across an old(ish) Haskell codebase in the future, tries to compile it, fails, has no idea why, and gives up, possibly leaving Haskell for good.

---

<div class="post-metadata">

**Author:** ![L0neGamer](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/l0negamer/32/4536_2.png) [@L0neGamer](https://discourse.haskell.org/u/L0neGamer)\
**Post date:** [February 21, 2025, 3:50pm UTC](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443/21 "2025-02-21T15:50:35Z")

</div>

It’s a question of how oldish it is, and whether we can give some nicer compiler errors. I doubt we’re going to finish this proposal in the immediate future (in fact, I’d recommend we hold off on the last phase for a handful of years), and we can hopefully add some compiler errors that inform the user to downgrade if they want this code.

With such timing, if after 2-3 years of warning on lawful definitions and about 12 of warning/erroring on unlawful, remaining packages must be rather old and unmaintained indeed.

[Next page](https://discourse.haskell.org/t/monad-of-no-return-next-steps/11443.md?page=2)
