Proposal: Laws for mtl classes

Andreas Abel andreas.abel at ifi.lmu.de
Tue Apr 23 07:15:16 UTC 2019


Good initiative, Li-yao!

The laws look sound, but are they complete?  For instance, there is a 
law stating that 'local' is a semigroup morphism, but it is not obvious 
to me that the laws also imply that 'local' is a monoid morphism.  I.e., is

   local id = id

implied?  (For a suitable notion of equality '='.)

To find out whether the laws are complete, I suggest to formalize the 
theory of these monad in a theorem prover like Agda and prove the 
completeness there.

Cheers,
Andreas

On 2019-04-21 19:43, Li-yao Xia wrote:
> Hello Libraries,
> 
> I am looking for feedback and discussion about the laws I am proposing 
> in the following pull requests (to save you the click, I will copy the 
> laws at the end of this email):
> 
> - MonadState and MonadReader: https://github.com/haskell/mtl/pull/61
> - MonadError: https://github.com/haskell/mtl/pull/62
> 
> Laws for the other classes will be proposed in the future.
> 
> Indeed, you may be surprised to learn that these classes have never had 
> documented laws, in spite of the often touted importance of laws in the 
> Haskell community: https://github.com/haskell/mtl/issues/5
> 
> There is a vague consensus that "classes should have laws", but with 
> very little guidance as to how to design those laws. My approach so far 
> mostly consists of "think very hard about all possible configurations, 
> relying on my experience in equational reasoning". There are papers 
> (some mentioned in the Github issue) discussing such laws, they are 
> useful as a starting point, but most of them focus on MonadState or 
> Alternative, and when MonadReader does appear, local is missing from the 
> picture. More crucially, beyond saying "it's the only thing I can think 
> of", it seems difficult to critically evaluate a given set of laws. Some 
> subtle variations can be quite challenging to compare, see for example 
> this post about two definitions of idempotency:
> 
> https://duplode.github.io/posts/idempotent-applicatives-parametricity-and-a-puzzle.html 
> 
> 
> Suggestions and additional references in this area are more than welcome.
> 
> As part of figuring things out, I wrote those mtl laws as QuickCheck 
> tests, in a style inspired by the checkers library:
> 
> https://github.com/Lysxia/checkers-mtl (still unreleased)
> 
> Further bikeshedding is likely to take place, and feel free to ask 
> questions to clarify anything.
> 
> - Could these laws be made simpler?
> - Are there additional laws that are expected to hold?
> - Are there reasonable instances that break those laws? For instance, I 
> decided to include laws that turn get and ask into noops if their 
> results are ignored, but could it make sense to allow them to have side 
> effects?
> 
> I have plans to publish more detailed write-ups about those proposed 
> laws (as blogposts or as part of the documentation), but even having 
> documented laws at all seems better than nothing now, if only to serve 
> as a basis for more concrete discussions.
> 
> Li-yao
> 
> ---
> 
> Class definitions and proposed laws reproduced below.
> 
> 
> -- MonadReader
> 
> class Monad m => MonadReader r m | m -> r where
>      {-# MINIMAL (ask | reader), local #-}
>      ask   :: m r
>      local :: (r -> r) -> m a -> m a
>      reader :: (r -> a) -> m a
> 
> {-
> m <*> ask   =   ask <**> m
> 
> ask >> pure x   =   pure x
> ask >>= \s1 -> ask >>= \s2 -> k s1 s2   =   ask >>= \s -> k s s
> 
> local f ask       = f <$> ask
> local g . local f = local (g . f)
> 
> -- local is a monad morphism from m to m
> local f (pure x)  = pure x
> local f (a >>= k) = local f a >>= \x -> local f (k x)
> 
> ask = reader id
>   -}
> 
> 
> -- MonadState
> 
> class Monad m => MonadState s m | m -> s where
>      {-# MINIMAL state | get, put #-}
>      get :: m s
>      put :: s -> m ()
>      state :: (s -> (a, s)) -> m a
> 
> {-
> get    >>= put    = pure ()
> put s  >>  get    = put s >> pure s
> put s1 >>  put s2 = put s2
> 
> get >> pure x   =   pure x
> get >>= \s1 -> get >>= \s2 -> k s1 s2   =   get >>= \s -> k s s
> 
> get   = state (\s -> (s, s))
> put s = state (\_ -> ((), s))
>   -}
> 
> 
> -- MonadError
> 
> class (Monad m) => MonadError e m | m -> e where
>      throwError :: e -> m a
>      catchError :: m a -> (e -> m a) -> m a
> 
> {-
> catchError (throwError e) h   = h e
> catchError (pure a) h         = pure a
> catchError (catchError m k) h = catchError m (\e -> catchError (k e) h)
> catchError (m >>= k) h        = tryError m >>= either h k
> -- tryError :: MonadError e m => m a -> m (Either e a)
> 
> catchError m throwError       = m
> throwError e >>= k            = throwError e
>   -}
> _______________________________________________
> Libraries mailing list
> Libraries at haskell.org
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries


More information about the Libraries mailing list