mtl 3.0 planning #116
Copy link
Copy link
Open
Description
Per discussions with @ekmett and elsewhere
- Factor
MonadReaderintoAskandLocalclasses - Factor
MonadWriterintoTellandListen, andPass - Reduce constraints where applicative (i.e.
FunctorTell,ApplicativeAsk) - Address Kmetto-senpai's reqeuest for laws and better docs
- Clear the existing PR backlog
- A proper ListT
- Handler-like algebraic
catch(see: https://paste.tomsmeding.com/aAYwOy7t) - etc...
Thoughts welcome
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
kozross commentedon May 8, 2022
MonadAccumwould need to gainMonadAskandMonadTellsuperclass constraints. I also feel we need to nail down precise laws, and restrict down some instances: for example,ContT r' (Reader r)is aMonadAsk, but not aMonadLocalas per #83.kozross commentedon May 8, 2022
Also, does it make sense to crack
MonadErrorintoMonadThrowandMonadCatch, paralleling the design choices for exception-chucking classes?kozross commentedon May 8, 2022
With regard to a proper
ListT, in order for this to 'belong' sensibly in this package, we need something likeMonadChoiceorMonadNonDet(I favour the latter). The best design for this is basically theMonadLogicoflogict(I literally can't think of another), and that comes with laws, which I appreciate. However, this would mean 'taking ownership' of the equivalent of two whole packages.gbaz commentedon May 10, 2022
add an update monad a la https://danel.ahman.ee/papers/types13postproc.pdf ?
kozross commentedon May 10, 2022
@gbaz I've considered this, but this would require a monoid action type class, which we'd also end up having to 'own'.
emilypi commentedon May 10, 2022
Ooo dibs
kozross commentedon May 10, 2022
@emilypi If we decide to go this way, we definitely need to consider the fundep question. I personally favour 'action determines state'. It does mean we basically can't provide almost any instances, though.
emilypi commentedon May 10, 2022
Uh. I have feelings about this. I've made several attempts and bounced off of the question of which which is approach is best for several reasons. On one hand, I hate biting the overlapping/overlappable/incoherent approach of using raw MPTC (i.e.
Act a x) without fundeps, because it becomes extremely easy to write nonsensical and conflicting instances forEndo xrather quickly.The solution, if we were to go down the overlapping road, would be to backpack a la @ekmett's Deltas in
coda. It would allow us to subclass particular actions relatively cleanly. But then we have to admit backpacking into the project and ahaha guess what problems we face then? Politics and nobody being able to build upon our work. AnD i aiN'T gOt TiME fOr thaT :DDDDOn the other hand,
fundepsrequires us to work with fundeps and proliferate a billion newtypes. I think if we were to go down that road, we'd have to commit to a set of core actions we care to support and let others write theactions-extraspackage that i'm not gonna maintain personally if they want to expand the repertoire.Regardless, we also run into the problem into the problem of committing this to
mtlor a package upstream ofmtland that's a tough sell.kozross commentedon May 10, 2022
I strongly oppose any kind of typechecker soup arising from not having fundeps on a multi-param type class. The biggest pain for me is that the most natural such fundep (action determines state) means that certain 'obvious' instances become literally un-writeable. Consider more-or-less the canonical implementation of the functionality of
MonadAsk, which would require something likeinstance Action () a: this falls apart immediately, since you're now asking()to determine basically everything. For what it's worth, this problem also arises in the other direction, and makes even less sense there.I definitely don't know how I feel about such a cost: it essentially pushes all instances onto users. However, I'd rather that than the alternatives, which is typechecker slurry or significant complexity. I don't know in how far Backpack would help this, but this means becoming immediately incompatible with Stack, for example. While I don't really care much for Stack, and consider it useless in 2022, I don't think many people will agree with me, especially on a package as 'core' as
mtl.kozross commentedon May 10, 2022
Also, could we add 'laws and tests for these'? I'm already laying the groundwork, but it'd be nice to have that too.
brprice commentedon Aug 31, 2022
One thing I have wanted a few times is a
MonadStateReadOnlysuperclass ofMonadState(and I guess dually aMonadStateWriteOnly) so I can mark (in a compiler-checked way!) helper functions as "this will only read the state, not modify it". Whilst you can do this by writing functions over a reader monad and translating that into state, this is awkward and does not work if you are using a reader for something else already (e.g. a global environment for your state machine, or an IO action for logging purposes etc).Unfortunately I don't actually know how feasible / sensible this factoring would be in practice.
emilypi commentedon Sep 2, 2022
From chats with Ed, which would also affect
transformers:L0neGamer commentedon Sep 16, 2025
I know that @sjshuck has considered continuning at least the typeclass splits, and I would like to caution warning especially due to breakage.
As it currently stands, there is not a good migration story when you are trying to break up or move around class methods for a class, meaning that there will be a very sudden ecosystem split between
MonadReaderandAsk, and that means that all packages that use one or the other cannot be really use the other without CPP in every package.As part of Monad Canonicity: Removing Method (>>) I was considering proposing a GHC extension,
SharedClassMembers, which would mean that subclasses could define methods for their parent classes: ghc-proposals/ghc-proposals#707.If we had this extension, then we could define
class (Ask r m, Local r m) => MonadReader r m | m -> ras a typeclass, add a warning to it, and then anyone defining MonadReader would get a warning but would also get well formed classes. This turns a complete ecosystem fracture (package x that has not updated to new mtl cannot be used with package y which has) into a ghc version fracture (pre-existing MonadReader instances can only be defined with ghc-SharedClassMembrs), and since mtl is a boot library I think that is an acceptable trade.Unfortunately I do not have the ability to re-propose the proposal currently, but I'm happy to help out and proof read if someone else wishes to.
sjshuck commentedon Sep 16, 2025
I haven't formulated enough of an opinion to propose anything beyond a not-broadcast slide deck at MuniHac, so just knee-jerk reaction for now. I had in mind that instead of the proposed
MonadAskthere would beMonadReader, just withoutlocal. All derivedMonadReaders would still work if they didn't calllocalanywhere; iflocalis needed,MonadLocalsubclass instances would have to be added by the user to support that. I want to survey big libraries like lens to see if they still work as-is—see what the damage would be.You envision
SharedClassMembersfor 9.16? Sooner?Glad you chimed in btw @L0neGamer.
L0neGamer commentedon Sep 16, 2025
Glad to share my experience. I had no particular plan for timings, but in the next year would be good to go with my other Mon(a|oi)d of no return proposal. As mentioned I am not currently championing this extension due to personal circumstances, but can help out with concepts and answering q's.