Skip to content
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

Missing laws #5

Open
ekmett opened this issue Mar 28, 2013 · 18 comments
Open

Missing laws #5

ekmett opened this issue Mar 28, 2013 · 18 comments
Assignees

Comments

@ekmett
Copy link
Member

ekmett commented Mar 28, 2013

The laws for MonadState, etc. are missing from the haddocks.

It may be a good idea to add them.

@ghost ghost assigned ekmett Mar 28, 2013
@ekmett
Copy link
Member Author

ekmett commented Mar 28, 2013

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.90.2560 provides an identical set of laws to the ones I'd expect.

@HeinrichApfelmus
Copy link

I'm currently mulling over my operational package again, and it appears to me that the following laws should hold for monad transformers:

MonadState

get = lift get
put = lift . put

MonadReader

ask = lift ask
local r (lift   m) = lift (local r m)
local r (return a) = return a
local r (m >>=  k) = local r m >>= local r . k

In particular, the last example shows how to lift a control operation in a unique way, something which I didn't think was possible.

These laws are not an a-priori characterization, they only hold for monad transformers. You need to walk down the transformer stack to see how they relate to each other. For instance, to see that

local r ask = fmap r ask

one has to unpeel all the intermediate lift until you end up at the base Reader monad.

(Actually, I think that a-priori laws are not a complete story, you also need to know how the instances are lifted through a monad transformer.)

@ekmett
Copy link
Member Author

ekmett commented Dec 3, 2013

I can at least see stating those as laws that MonadReader, MonadState, etc. instances that lift over a transformer can satisfy. Otherwise, obviously, the equations above don't hold in general.

@HeinrichApfelmus
Copy link

The class for the writer monad is weird because it has so many control operations. I think the laws for a monad transformer should be:

MonadWriter

tell = lift . tell

listen (lift   m) = lift (listen m)
listen (return a) = return (a,mempty)
listen (m >>=  k) = listen m `combine` listen . k
    where
    combine m k = do
        (a, w1) <- m
        (b, w2) <- k a
        return (b, w1 `mappend` w2)

pass (lift   m)     = lift (pass m)
pass (return (a,f)) = return (a, f mempty)
pass (m >>=  k)     = m `combine` k
    where
    combine m k = do
        (a, w1) <- listen m
        pass (tell w1 >> k a)

The laws for listen resemble a monad morphism, whereas pass is just wacky. Weirdly enough, both seem to reproduce the definition of the writer monad in the first place.

@Lysxia
Copy link

Lysxia commented Dec 7, 2017

@ekmett That paper states MonadState laws in terms of get and modify (called update (u) in the paper).

modify f >> modify g   =   modify (g . f)  -- modify-modify
get >>= \s1 -> get -> \s2 -> return (s1, s2)   =   get >>= \s -> return (s, s)   -- get-get
get >>= \s1 -> modify f >> get >>= \s2 -> return (s1, s2)   =   get >>= \s -> modify f >> return (s, f s)   -- get-modify-get

Since mtl's MonadState is given by get and put, it seems simpler to reformulate laws in these terms. The four laws obtained from the different ways of sequencing two of put or get imply the above, but I'm wondering whether these are too strong for some useful instances, where get and put may have more side-effects besides querying/modifying the state.

get >>= \s1 -> get -> \s2 -> return (s1, s2)   =   get >>= \s -> return (s, s)   -- get-get (the same)
get >>= \_ -> put s   =   put s   -- get-put
put s1 >> put s2   =   put s2   -- put-put
put s >> get    =   put s >> return s   -- put-get

The following get-put-2 seems a bit more "economical" by making use of just two class method calls instead of three in get-put; together with get-get, it implies get-put.

get >>= \s -> put s   =   return ()   -- get-put-2

In a similar vein, one might think of get-void below:

void get = return ()   -- get-void

Are the equations above general enough? Is there a litmus test for sufficiently general MonadState laws (and for other mtl classes)?

This may just be my wishful thinking, but one nice-sounding property is that any sequence of get and put can be rewritten as a single get followed by a single put, and maybe a return: get s >>= put (f s) >> return (g s). To that end, get-get, put-put and put-get take care of most cases, then get-put-2 seems just right to finish that "normalization".


EDIT: I have a lot to read; I could find these laws mentioned in various places. http://gallium.inria.fr/blog/lawvere-theories-and-monads/

@Lysxia
Copy link

Lysxia commented Dec 7, 2017

For MonadReader, may I suggest the following:

(,) <$> ask <*> ask   =   (\r -> (r, r)) <$> ask   -- ask-ask
local f ask   =   fmap f ask   -- local-ask
local f . local g   =   local (g . f)   -- local-local

local f (return x)   =   return x   -- local-M-return
local f (m >>= \x -> k x)   =   local f m >>= \x -> local f (k x)   -- local-M-bind

@ocharles
Copy link

Could your ask-ask law just be

ask >> ask = ask

?

@Lysxia
Copy link

Lysxia commented Dec 14, 2017

@ocharles Right, that looks equivalent and simpler. Formally speaking, I'm missing a step to prove that ask >> ask = ask implies (,) <$> ask <*> ask = (\r -> (r, r)) <$> ask; does it need some extra assumption about the monad (that might always hold in the cases we care about)?

@ekmett
Copy link
Member Author

ekmett commented Dec 14, 2017

You could probably go even stronger with something like

    m <*> ask = ask <**> m
    ask *> m = m = m <* ask

to show that ask commutes with everything, has no other side-effects and no monadic action can affect the answer of ask.

@ekmett
Copy link
Member Author

ekmett commented Dec 14, 2017

Also: at the very least, reader, writer and state are all monad homomorphisms, just like lift. (They are even almost always monad monomorphisms, but for an annoying technical corner case involving the terminal monad transformer.)

@Lysxia
Copy link

Lysxia commented Dec 14, 2017

Interesting, these homomorphisms imply a lot of the laws.

For example, obviously in (->) we can check that ask >> ask = ask. Now in a general MonadReader, provided ask = reader ask, we have ask >> ask = reader ask >> reader ask = reader (ask >> ask) = reader ask = ask.

@recursion-ninja
Copy link

Any progress on finalizing these laws and adding them to the documentation?

@Lysxia
Copy link

Lysxia commented Mar 3, 2019

Thanks for the reminder @recursion-ninja . I think this issue was a victim of "perfect is the enemy of good". There are so many ways to present the laws, and comparing them all against each other is a deep rabbit hole. I just opened PR #61 to get things started.

@Zemyla
Copy link

Zemyla commented Oct 20, 2019

The get-put laws can be easier described if they're defined in terms of state, I think.

-- Laws for get and put
get = state $ \s -> (s, s)
put s = state $ const $ ((), s)

-- Law for state joining
state f >>= state . g = state $ uncurry g . f

Also, I'm wondering whether MonadCont has any laws. From what I can tell, these sound reasonable:

-- These laws specify the basic behavior of the passed continuation.

-- callCC itself has no side effects.
callCC (const m) = m

-- The continuation passed returns the given value.
callCC ($ a) = pure a

-- It also does so in the presence of side effects.
callCC ((>>=) m) = m

-- The return value of the continuation passed is actually Void, so it doesn't "really" return.
callCC f = callCC (f . fmap absurd)

-- And then this law I think would allow decomposition of the behavior of callCC by induction into the base cases. Though I'm not sure if it actually works that way.
callCC (\k -> f k >>= (\a -> k a >>= g k)) = callCC f

I'm also not sure whether the situation with StateT on MonadCont would screw things up.

@Lysxia
Copy link

Lysxia commented Oct 25, 2019

@chessai
Copy link
Member

chessai commented Oct 25, 2019

Keep forgetting to note here, that as soon as the laws are decided on and a PR is merged, i will add them to quickcheck-classes and hedgehog-classes.

@treeowl
Copy link

treeowl commented Aug 22, 2021

It'd be great to actually get this sorted. The control operations are a horrible mess for things like list-t and logict, and not even knowing how they're supposed to behave makes implementation practically impossible.

treeowl added a commit to treeowl/list-t that referenced this issue Aug 26, 2021
I've pretty well convinced myself that this obeys the `MonadReader`
laws
[proposed by Heinrich Apfelmus](haskell/mtl#5 (comment))
and the ones
[proposed by Li-yao
Xia](haskell/mtl#5 (comment)),
so I think it should be added.
treeowl added a commit to treeowl/list-t that referenced this issue Aug 26, 2021
I've pretty well convinced myself that this obeys the `MonadReader`
laws
[proposed by Heinrich Apfelmus](haskell/mtl#5 (comment))
and the ones
[proposed by Li-yao
Xia](haskell/mtl#5 (comment)),
so I think it should be added.
treeowl added a commit to treeowl/list-t that referenced this issue Aug 26, 2021
* I've pretty well convinced myself that this instance obeys the `MonadReader`
  laws
  [proposed by Heinrich Apfelmus](haskell/mtl#5 (comment))
  and the ones
  [proposed by Li-yao Xia](haskell/mtl#5 (comment)),
  so I think it should be added.

* The `MonadState` instance is trivial.
treeowl added a commit to treeowl/list-t that referenced this issue Aug 26, 2021
* I've pretty well convinced myself that this instance obeys the `MonadReader`
  laws
  [proposed by Heinrich Apfelmus](haskell/mtl#5 (comment))
  and the ones
  [proposed by Li-yao Xia](haskell/mtl#5 (comment)),
  so I think it should be added.

* The `MonadState` instance is trivial.
treeowl added a commit to treeowl/list-t that referenced this issue Aug 26, 2021
* I've pretty well convinced myself that this instance obeys the `MonadReader`
  laws
  [proposed by Heinrich Apfelmus](haskell/mtl#5 (comment))
  and the ones
  [proposed by Li-yao Xia](haskell/mtl#5 (comment)),
  so I think it should be added.

* The `MonadState` instance is trivial.
treeowl added a commit to treeowl/list-t that referenced this issue Aug 26, 2021
* I've pretty well convinced myself that this instance obeys the `MonadReader`
  laws
  [proposed by Heinrich Apfelmus](haskell/mtl#5 (comment))
  and the ones
  [proposed by Li-yao Xia](haskell/mtl#5 (comment)),
  so I think it should be added.

* The `MonadState` instance is trivial.

Closes nikita-volkov#23.
treeowl added a commit to treeowl/list-t that referenced this issue Aug 26, 2021
* I've pretty well convinced myself that this instance obeys the `MonadReader`
  laws
  [proposed by Heinrich Apfelmus](haskell/mtl#5 (comment))
  and the ones
  [proposed by Li-yao Xia](haskell/mtl#5 (comment)),
  so I think it should be added.

* The `MonadState` instance is trivial.

Closes nikita-volkov#23.
treeowl added a commit to treeowl/list-t that referenced this issue Aug 26, 2021
* I've pretty well convinced myself that this instance obeys the `MonadReader`
  laws
  [proposed by Heinrich Apfelmus](haskell/mtl#5 (comment))
  and the ones
  [proposed by Li-yao Xia](haskell/mtl#5 (comment)),
  so I think it should be added.

* The `MonadState` instance is trivial.

Closes nikita-volkov#23.
@treeowl
Copy link

treeowl commented Aug 26, 2021

@HeinrichApfelmus, I'd expect a law relating listen to tell, in addition to the ones you gave above years ago.

listen (tell w) = ((), w) <$ tell w

@emilypi emilypi mentioned this issue May 8, 2022
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants