-
Notifications
You must be signed in to change notification settings - Fork 46
Reorganize Monad laws, prove Applicative laws #412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reorganize Monad laws, prove Applicative laws #412
Conversation
|
Thanks for the PR! Regarding incremental development: ideally there should be a gradual path:
The current design allows all three: one can just implement a Another argument against merging the laws into the class is that sometimes, Haskell programmers actually do implement non-lawful monads, and then it would be inconsistent to postulate that such a monad is lawful. |
Yes, though the argument about making these postulates would equally apply to this situation.
That is true, but specifically for Just for the record: The benefit of merging the two classes would be that any general proposition about monads has a shorter type, because it only needs to mention |
|
It should be possible to add quotient types (with postulates + perhaps a rewrite rule) so I'd say that's not a showstopper for this PR. But since it's not really connected to this issue, please make a separate issue for that. I do agree that not needing a separate constraint for the laws is nice. But this could in principle also be achieved by defining a bundled class |
omelkonian
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall, I've just pointed some design changes to make the approach more maintanable.
lib/base/Haskell/Law/Monad/Def.agda
Outdated
| overlap ⦃ default ⦄ : IsDefaultMonad m | ||
| overlap ⦃ monad ⦄ : MonadLaws m |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I agree with the MonadLaws/IsLawfulMonad separation, I don't think we should also separate IsDefaultMonad/MonadLaws; one only has the monad laws proven when the user's instance has implemented correctly the default fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nomenclature "monad laws" typically refers to the three identities I originally put in the record MonadLaws — and only those three identities.
For example, Hutton, "Programming in Haskell", (2018), p. 174 writes:
Monad laws
In a similar manner to functors and applicatives, the two monadic primitives are required to satisfy some equational laws:
return x >>= f = f x mx >>= return = mx (mx >>= f) >>= g = mx >>= (\ x -> (f x >>= g))
The identities that I had put in the IsDefaultMonad record concern the definition of functions that are traditional or relate to superclasses, but are not part of the "monad laws", such as pure, >>, fmap, and (<*>).
In other words, in principle, it is possible for a user instance of Monad to implement the "monad laws" correctly while implementing the traditional and superclass functions incorrectly.
(The documentation for Control.Monad in the base package tries to help by listing (a subset of) relevant laws, but it's not an authoritative list of the "monad laws".)
In that light, I figured that it would make sense for me to mirror the conceptual grouping of laws with two explicit records. This has the additional benefit that I can ask the question whether IsDefaultMonad should be merged with the Monad class, while keeping MonadLaws separate. The idea is that IsDefaultMonad concerns the definitions of certain functions, while MonadLaws concerns properties that do not have the form of a definition.
(I want to note that using this conceptual grouping makes it easier to ask the question of "Is there a law for each function definition?", which in turn makes it possible to discover that def->>->>= is not provable from the implementation before this pull request.)
That said, I don't mind lumping MonadLaws and IsDefaultMonad together — this would reflect a different conceptual grouping along the lines of "function definitions" (Monad) and "any properties those functions may have" (MonadLaws).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The identities that I had put in the IsDefaultMonad record concern the definition of functions that are traditional or relate to superclasses, but are not part of the "monad laws", such as pure, >>, fmap, and (<*>).
Then it's the wrong name :P
That said, I don't mind lumping MonadLaws and IsDefaultMonad together
Just to be clear, I'm not suggesting a single record with all laws, we've confused two separate orthogonal issues:
- separating the laws about default methods in
IsDefaultMonad - separating the monad laws into the "pure" Monad laws and interactions with superclasses
I completely agree with (1), but done in a DRY fashion.
I don't have a strong opinion for (2), but it can wait for a future PR when the separation has at least a single use.
| def-fmap->>= : ∀ {a b} (f : a → b) (ma : m a) | ||
| → fmap f ma ≡ ma >>= (return ∘ f) | ||
|
|
||
| def-<*>->>= : ∀ {a b} (mab : m (a → b)) (ma : m a) | ||
| → (mab <*> ma) ≡ (mab >>= (λ f → (ma >>= (λ x → return (f x))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to think of default methods and superclasses as completely separate issues/laws.
These can still remain in their original place in MonadLaws, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(See my other comment #412 (comment) . In short, the definitions of superclass methods are not part of the nomenclature of the three "monad laws", hence me grouping them with the other definition-like properties.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still doesn't make sense to have monad-functor laws inside the IsDefaultMonad.
lib/base/Haskell/Law/Monad/Def.agda
Outdated
| -- Monad laws, grouped | ||
| -- | ||
| -- We have split the 'IsLawfulMonad' class into different groups, | ||
| -- because 'IsLawfulApplicative' can be proven from the other groups. | ||
| -- At usage sites, it is convenient to automatically have | ||
| -- an instance 'IsLawfulApplicative' in scope whenever an instance of | ||
| -- 'IsLawfulMonad' is in scope, hence we use it as a superclass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too wordy and a bit confusing. If you apply my suggested refactoring, rephrase as:
-- Monad laws
--
-- Note that `MonadLaws` do not yet require that the superclasses are themselves lawful; this is deferred to `IsLawfulMonad` as these can sometimes be proven via the `MonadLaws`.
(the comment about superclass instances in scope is irrelevant)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed the comment on PreLawfulMonad in the manner you suggested.
That done, I do think that the only difference between PreLawfulMonad and IsLawfulMonad is the scope of IsLawfulApplicative and IsLawfulFunctor, because we have proofs prop-PreLawfulMonad→IsLawfulApplicative and prop-PreLawfulMonad→IsLawfulFunctor.
|
I agree with @jespercockx in keeping
Yep, that's not a valid argument for merging. One could also make this construction more generic so that it works for any "typeclass conjuction" (although it breaks once you move to higher universe levels..., but since we only use |
a7c6856 to
832fc40
Compare
|
@HeinrichApfelmus I see you pushed some changes here, could you please give a summary of the status of this PR? Are there any remaining questions to be addressed? |
The status of this PR is "blocked due to disagreement". 😅 The two main pending items are:
|
|
I don't either change is really essential to merging this PR. |
|
@jespercockx If you're fine with Monad-Functor laws being in |
|
I will combine |
832fc40 to
954fac8
Compare
954fac8 to
88f511c
Compare
This pull request reorganizes the
IsLawfulMonadclass.The main reason for this reorganization is that
IsLawfulFunctorandIsLawfulApplicativeare actually consequences ofMonadLawsandIsDefaultMonad. However, this fact is difficult to use in the old definition as the other two classes are used as superclasses.Context: #411
Comments
While not in the scope of this pull request, possible future reorganizations are:
Drop the
IsLawfulApplicativesuperclass fromIsLawfulMonadand make it a global consequence instead, i.e. add a globalinstance _ : {{IsLawfulMonad m}} → IsLawfulApplicative m.This has the slight drawback that the existing instance of
IsLawfulApplicativeneed to be removed while making sure thatHaskell.Law.Applicativere-exports the global instance. Then again, theIsLawfulMonadinstance is the easiest way to proveIsLawfulApplicative, and in the spirit of proof-irrelevance, we don't actually care how we have provenIsLawfulMonad.Merge the
IsDefaultMonadclass into theMonadclass.Reason: In Haskell, it is traditional to expect the
Monadconstraint to imply the laws mentioned in the documentation. For usage sites, this means thatMonadis sufficient, theIsLawfulMonadconstraint is conceptually implied by theMonadclass. This conceptual model works until we actually have to formalize proofs, at which point we want to develop proofs incrementally. For incremental development of proofs, the separation between "implementation" (Monad) and "properties" (IsLawfulMonad) is very useful, but I would argue that this case rarely happens specifically for theMonadclass, where instances are seldomly defined by library users. (In contrast,EqandOrdare frequently defined by library users for their own data types, and the separation ofIsLawfulEqandIsLawfulOrdis more valueable.)As a first step towards conceptual unification, I would propose to merge
IsDefaultMonadinto theMonadclass, as the equality to the default implementations is uncontroversial.Merge the
IsLawfulMonadclass into theMonadclass. Same reasoning as in 2., but more extreme.