Petr P | 27 Jan 21:30 2013
Picon

suggestions for improving MonadWriter

  Dear maintainers,

I have two suggestions for MonadWriter:

(1) Remove the "Monoid w" constraint from the definition.

The constraints prevent creating new instances of the class that have only an implied monoid. For example, I needed to create a simple writer which always stores the last written element. I had to wrap it into Last, which was a nuisance for users of my library. Without the constraint, my instance would be quite simpler and still satisfying all the laws. There are many other similar use cases, like counting the number of written values (and disregarding their actual content) etc.

The constraint is meant to ensure that instances of that class obey the monad laws. But it's not the responsibility of a type class that its instances satisfy the laws. They could violate them even without this constraints. Instead, this constraint should be specified (and it is) in the definition of their instances.

It has been discussed in haskell-cafe <http://www.haskell.org/pipermail/haskell-cafe/2012-December/thread.html#105088> with arguments for and against.


(2) Add

  -- | <at> contained m <at> executes the action <at> m <at> in a contained environment and
  -- returns its value and its output. The current output is not modified.  
  contained :: m a -> m (a, w)

to MonadWriter.

This generalizes "pass" and "listen"  and has it's a sort of inverse to "writer" with these simple laws:

  writer <=< contained   = id
  contained . writer     = return

It seems as a understandable set of laws that its instances should obey.

It also expresses the same concept as "runWriterT" does, but inside the type class. In particular, for "WriterT" we have

  contained :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
  contained = lift . runWriterT

Current instances won't be affected as "contained" can be expressed using "pass" and "listen" (and vice versa).
Full details available at
[There "contained" is expressed without the "Monoid w" constraint as suggested in (1). If we keep the constraint, "contained" can be expressed more simply as
  containde k = pass (listen k >>= \x -> return (x, const mempty)).

Also, "contained" isn't probably a good name, I just couldn't think of anything better.]


   Best regards,
   Petr Pudlak

_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries
Roman Cheplyaka | 27 Jan 22:37 2013

Re: suggestions for improving MonadWriter

* Petr P <petr.mvd <at> gmail.com> [2013-01-27 21:30:31+0100]
> (1) Remove the "Monoid w" constraint from the definition.

Agree.

> (2) Add
> 
>   -- |  <at> contained m <at>  executes the action  <at> m <at>  in a contained environment and
>   -- returns its value and its output. The current output is not modified.
>   contained :: m a -> m (a, w)
> 
> to MonadWriter.

No opinion on this one.

Roman
Andreas Abel | 28 Jan 08:44 2013
Picon

Re: suggestions for improving MonadWriter

On 27.01.13 10:37 PM, Roman Cheplyaka wrote:
> * Petr P <petr.mvd <at> gmail.com> [2013-01-27 21:30:31+0100]
>> (1) Remove the "Monoid w" constraint from the definition.
>
> Agree.

Same. +1

>> (2) Add
>>
>>    -- |  <at> contained m <at>  executes the action  <at> m <at>  in a contained environment and
>>    -- returns its value and its output. The current output is not modified.
>>    contained :: m a -> m (a, w)
>>
>> to MonadWriter.
>
> No opinion on this one.

Same. -0.1

--

-- 
Andreas Abel  <><      Du bist der geliebte Mensch.

Theoretical Computer Science, University of Munich
Oettingenstr. 67, D-80538 Munich, GERMANY

andreas.abel <at> ifi.lmu.de
http://www2.tcs.ifi.lmu.de/~abel/
Iavor Diatchki | 28 Jan 07:18 2013
Picon

Re: suggestions for improving MonadWriter

Hi Petr,

you might also consider using `monadLib`, it is an alternative to MTL that I and some of my colleagues have used for a few years now.  It does both of the things that you suggest (i.e., the `Monoid` constraints are on the instances, not the class, and it provides a method to gather collected output, in `monadLib` this operation is called `collect`).  Generally `monadLib` provides similar functionality to MTL but with a number of improvements like the ones you suggest (e.g., same functionality with fewer types, improved support for working with continuations, a correct implementation of backtracking, etc.)

Happy hacking,
-Iavor



On Sun, Jan 27, 2013 at 12:30 PM, Petr P <petr.mvd <at> gmail.com> wrote:
  Dear maintainers,

I have two suggestions for MonadWriter:

(1) Remove the "Monoid w" constraint from the definition.

The constraints prevent creating new instances of the class that have only an implied monoid. For example, I needed to create a simple writer which always stores the last written element. I had to wrap it into Last, which was a nuisance for users of my library. Without the constraint, my instance would be quite simpler and still satisfying all the laws. There are many other similar use cases, like counting the number of written values (and disregarding their actual content) etc.

The constraint is meant to ensure that instances of that class obey the monad laws. But it's not the responsibility of a type class that its instances satisfy the laws. They could violate them even without this constraints. Instead, this constraint should be specified (and it is) in the definition of their instances.

It has been discussed in haskell-cafe <http://www.haskell.org/pipermail/haskell-cafe/2012-December/thread.html#105088> with arguments for and against.


(2) Add

  -- | <at> contained m <at> executes the action <at> m <at> in a contained environment and
  -- returns its value and its output. The current output is not modified.  
  contained :: m a -> m (a, w)

to MonadWriter.

This generalizes "pass" and "listen"  and has it's a sort of inverse to "writer" with these simple laws:

  writer <=< contained   = id
  contained . writer     = return

It seems as a understandable set of laws that its instances should obey.

It also expresses the same concept as "runWriterT" does, but inside the type class. In particular, for "WriterT" we have

  contained :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
  contained = lift . runWriterT

Current instances won't be affected as "contained" can be expressed using "pass" and "listen" (and vice versa).
Full details available at
[There "contained" is expressed without the "Monoid w" constraint as suggested in (1). If we keep the constraint, "contained" can be expressed more simply as
  containde k = pass (listen k >>= \x -> return (x, const mempty)).

Also, "contained" isn't probably a good name, I just couldn't think of anything better.]


   Best regards,
   Petr Pudlak


_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries
Edward A Kmett | 28 Jan 16:09 2013
Picon

Re: suggestions for improving MonadWriter

I've often wanted to remove the Monoid constraint, so I'm a strong +1 on the first one.

The second one I'm -1 on, given that it can be expressed with the existing combinators and due in small part to a more ideological concern:

Over time I've come to view cramming pass/listen and local into the respective MonadWriter and MonadReader classes as a mistake. If we had the hierarchy to do over, I'd probably want them split into separate subclasses. This would permit more instances involving Cont, logging to disk, etc.

Also, the former can be done purely within the mtl, while the latter drags transformers into it.

-Edward

On Jan 27, 2013, at 3:30 PM, Petr P <petr.mvd <at> gmail.com> wrote:

  Dear maintainers,

I have two suggestions for MonadWriter:

(1) Remove the "Monoid w" constraint from the definition.

The constraints prevent creating new instances of the class that have only an implied monoid. For example, I needed to create a simple writer which always stores the last written element. I had to wrap it into Last, which was a nuisance for users of my library. Without the constraint, my instance would be quite simpler and still satisfying all the laws. There are many other similar use cases, like counting the number of written values (and disregarding their actual content) etc.

The constraint is meant to ensure that instances of that class obey the monad laws. But it's not the responsibility of a type class that its instances satisfy the laws. They could violate them even without this constraints. Instead, this constraint should be specified (and it is) in the definition of their instances.

It has been discussed in haskell-cafe <http://www.haskell.org/pipermail/haskell-cafe/2012-December/thread.html#105088> with arguments for and against.


(2) Add

  -- | <at> contained m <at> executes the action <at> m <at> in a contained environment and
  -- returns its value and its output. The current output is not modified.  
  contained :: m a -> m (a, w)

to MonadWriter.

This generalizes "pass" and "listen"  and has it's a sort of inverse to "writer" with these simple laws:

  writer <=< contained   = id
  contained . writer     = return

It seems as a understandable set of laws that its instances should obey.

It also expresses the same concept as "runWriterT" does, but inside the type class. In particular, for "WriterT" we have

  contained :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
  contained = lift . runWriterT

Current instances won't be affected as "contained" can be expressed using "pass" and "listen" (and vice versa).
Full details available at
[There "contained" is expressed without the "Monoid w" constraint as suggested in (1). If we keep the constraint, "contained" can be expressed more simply as
  containde k = pass (listen k >>= \x -> return (x, const mempty)).

Also, "contained" isn't probably a good name, I just couldn't think of anything better.]


   Best regards,
   Petr Pudlak

_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries
Petr P | 28 Jan 18:10 2013
Picon

Re: suggestions for improving MonadWriter

Edward,

2013/1/28 Edward A Kmett <ekmett <at> gmail.com>
I've often wanted to remove the Monoid constraint, so I'm a strong +1 on the first one.

The second one I'm -1 on, given that it can be expressed with the existing combinators and due in small part to a more ideological concern:

Over time I've come to view cramming pass/listen and local into the respective MonadWriter and MonadReader classes as a mistake. If we had the hierarchy to do over, I'd probably want them split into separate subclasses. This would permit more instances involving Cont, logging to disk, etc.

I strongly support your idea. Currently, MonadWriter and MonadReader just reflect ReaderT and WriterT so that we can make monad stacks. But they don't reflect the ideas - a monad we write to or read from, without any additional constraints. I'd really love to have them split up: A more general class that has just 'tell' and 'writer', and another that also has 'listen' and 'pass' (perhaps also 'contained').
I tried to discuss it in haskell-cafe once <http://www.mail-archive.com/haskell-cafe <at> haskell.org/msg101756.html>, but without any feedback on the idea, so I thought everybody is happy with the current state.

In this case, it seems that nothing important would break by this change, because IMHO there are very very little custom instances of MonadWriter/Reader. I'm also a big fan of this proposal: http://hackage.haskell.org/trac/ghc/wiki/DefaultSuperclassInstances
If it were implemented and standardized, we could refactor type classes without breaking existing code (or breaking it just very little).
[In particular, it'd be possible to make Monad a subclass of Applicative, something I've always regretted we don't have.]

Other than that, I'm not sure how adding "contained" goes against this idea. If we split MonadWriter into two type classes, "contained" would simply go into the one with "listen" and "pass".

Best regards,
Petr

 

Also, the former can be done purely within the mtl, while the latter drags transformers into it.



-Edward

On Jan 27, 2013, at 3:30 PM, Petr P <petr.mvd <at> gmail.com> wrote:

  Dear maintainers,

I have two suggestions for MonadWriter:

(1) Remove the "Monoid w" constraint from the definition.

The constraints prevent creating new instances of the class that have only an implied monoid. For example, I needed to create a simple writer which always stores the last written element. I had to wrap it into Last, which was a nuisance for users of my library. Without the constraint, my instance would be quite simpler and still satisfying all the laws. There are many other similar use cases, like counting the number of written values (and disregarding their actual content) etc.

The constraint is meant to ensure that instances of that class obey the monad laws. But it's not the responsibility of a type class that its instances satisfy the laws. They could violate them even without this constraints. Instead, this constraint should be specified (and it is) in the definition of their instances.

It has been discussed in haskell-cafe <http://www.haskell.org/pipermail/haskell-cafe/2012-December/thread.html#105088> with arguments for and against.


(2) Add

  -- | <at> contained m <at> executes the action <at> m <at> in a contained environment and
  -- returns its value and its output. The current output is not modified.  
  contained :: m a -> m (a, w)

to MonadWriter.

This generalizes "pass" and "listen"  and has it's a sort of inverse to "writer" with these simple laws:

  writer <=< contained   = id
  contained . writer     = return

It seems as a understandable set of laws that its instances should obey.

It also expresses the same concept as "runWriterT" does, but inside the type class. In particular, for "WriterT" we have

  contained :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
  contained = lift . runWriterT

Current instances won't be affected as "contained" can be expressed using "pass" and "listen" (and vice versa).
Full details available at
[There "contained" is expressed without the "Monoid w" constraint as suggested in (1). If we keep the constraint, "contained" can be expressed more simply as
  containde k = pass (listen k >>= \x -> return (x, const mempty)).

Also, "contained" isn't probably a good name, I just couldn't think of anything better.]


   Best regards,
   Petr Pudlak

_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries
Edward Kmett | 28 Jan 18:49 2013
Picon

Re: suggestions for improving MonadWriter

To be clear, I'm not yet making a concrete proposal for breaking up MonadReader and MonadWriter. Doing so would be fairly painful for at least some users in the short term. There are a lot of custom MonadReader and MonadWriter instances out there, and it also affects the signature of all combinators that consume these types that use pass, local or listen. That said the latter is probably only about 10% of the code that uses ask/tell. Moreso in the reader case than the writer case.

Given the number of users who have to support multiple platform releases, a concrete proposal would have to address the upgrade path, possibly including making MonadLocal and MonadListen stub subclasses a version or so early and then moving methods over later, etc. so users can upgrade appropriately, and library authors can follow suit, permitting both an upgrade path or deciding to rip the bandaid off in one go. The logistics of this are somewhat convoluted, and if we're going to go through that pain, it may be worth addressing other concerns at the same time.

-Edward

On Jan 28, 2013, at 12:10 PM, Petr P <petr.mvd <at> gmail.com> wrote:

Edward,

2013/1/28 Edward A Kmett <ekmett <at> gmail.com>
I've often wanted to remove the Monoid constraint, so I'm a strong +1 on the first one.

The second one I'm -1 on, given that it can be expressed with the existing combinators and due in small part to a more ideological concern:

Over time I've come to view cramming pass/listen and local into the respective MonadWriter and MonadReader classes as a mistake. If we had the hierarchy to do over, I'd probably want them split into separate subclasses. This would permit more instances involving Cont, logging to disk, etc.

I strongly support your idea. Currently, MonadWriter and MonadReader just reflect ReaderT and WriterT so that we can make monad stacks. But they don't reflect the ideas - a monad we write to or read from, without any additional constraints. I'd really love to have them split up: A more general class that has just 'tell' and 'writer', and another that also has 'listen' and 'pass' (perhaps also 'contained').
I tried to discuss it in haskell-cafe once <http://www.mail-archive.com/haskell-cafe <at> haskell.org/msg101756.html>, but without any feedback on the idea, so I thought everybody is happy with the current state.

In this case, it seems that nothing important would break by this change, because IMHO there are very very little custom instances of MonadWriter/Reader. I'm also a big fan of this proposal: http://hackage.haskell.org/trac/ghc/wiki/DefaultSuperclassInstances
If it were implemented and standardized, we could refactor type classes without breaking existing code (or breaking it just very little).
[In particular, it'd be possible to make Monad a subclass of Applicative, something I've always regretted we don't have.]

Other than that, I'm not sure how adding "contained" goes against this idea. If we split MonadWriter into two type classes, "contained" would simply go into the one with "listen" and "pass".

Best regards,
Petr

 

Also, the former can be done purely within the mtl, while the latter drags transformers into it.



-Edward

On Jan 27, 2013, at 3:30 PM, Petr P <petr.mvd <at> gmail.com> wrote:

  Dear maintainers,

I have two suggestions for MonadWriter:

(1) Remove the "Monoid w" constraint from the definition.

The constraints prevent creating new instances of the class that have only an implied monoid. For example, I needed to create a simple writer which always stores the last written element. I had to wrap it into Last, which was a nuisance for users of my library. Without the constraint, my instance would be quite simpler and still satisfying all the laws. There are many other similar use cases, like counting the number of written values (and disregarding their actual content) etc.

The constraint is meant to ensure that instances of that class obey the monad laws. But it's not the responsibility of a type class that its instances satisfy the laws. They could violate them even without this constraints. Instead, this constraint should be specified (and it is) in the definition of their instances.

It has been discussed in haskell-cafe <http://www.haskell.org/pipermail/haskell-cafe/2012-December/thread.html#105088> with arguments for and against.


(2) Add

  -- | <at> contained m <at> executes the action <at> m <at> in a contained environment and
  -- returns its value and its output. The current output is not modified.  
  contained :: m a -> m (a, w)

to MonadWriter.

This generalizes "pass" and "listen"  and has it's a sort of inverse to "writer" with these simple laws:

  writer <=< contained   = id
  contained . writer     = return

It seems as a understandable set of laws that its instances should obey.

It also expresses the same concept as "runWriterT" does, but inside the type class. In particular, for "WriterT" we have

  contained :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
  contained = lift . runWriterT

Current instances won't be affected as "contained" can be expressed using "pass" and "listen" (and vice versa).
Full details available at
[There "contained" is expressed without the "Monoid w" constraint as suggested in (1). If we keep the constraint, "contained" can be expressed more simply as
  containde k = pass (listen k >>= \x -> return (x, const mempty)).

Also, "contained" isn't probably a good name, I just couldn't think of anything better.]


   Best regards,
   Petr Pudlak

_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries
Iavor Diatchki | 29 Jan 08:31 2013
Picon

Re: suggestions for improving MonadWriter

Hi,

just for the record, this split is already made in `MonadLib` (e.g., monadLib's `collect` method, which corresponds to what Petr called `contained`, is in its own class called `RunWriterM`;  there are similar classes for readers and exceptions).

-Iavor


On Mon, Jan 28, 2013 at 9:49 AM, Edward Kmett <ekmett <at> gmail.com> wrote:
To be clear, I'm not yet making a concrete proposal for breaking up MonadReader and MonadWriter. Doing so would be fairly painful for at least some users in the short term. There are a lot of custom MonadReader and MonadWriter instances out there, and it also affects the signature of all combinators that consume these types that use pass, local or listen. That said the latter is probably only about 10% of the code that uses ask/tell. Moreso in the reader case than the writer case.

Given the number of users who have to support multiple platform releases, a concrete proposal would have to address the upgrade path, possibly including making MonadLocal and MonadListen stub subclasses a version or so early and then moving methods over later, etc. so users can upgrade appropriately, and library authors can follow suit, permitting both an upgrade path or deciding to rip the bandaid off in one go. The logistics of this are somewhat convoluted, and if we're going to go through that pain, it may be worth addressing other concerns at the same time.

-Edward

On Jan 28, 2013, at 12:10 PM, Petr P <petr.mvd <at> gmail.com> wrote:

Edward,

2013/1/28 Edward A Kmett <ekmett <at> gmail.com>
I've often wanted to remove the Monoid constraint, so I'm a strong +1 on the first one.

The second one I'm -1 on, given that it can be expressed with the existing combinators and due in small part to a more ideological concern:

Over time I've come to view cramming pass/listen and local into the respective MonadWriter and MonadReader classes as a mistake. If we had the hierarchy to do over, I'd probably want them split into separate subclasses. This would permit more instances involving Cont, logging to disk, etc.

I strongly support your idea. Currently, MonadWriter and MonadReader just reflect ReaderT and WriterT so that we can make monad stacks. But they don't reflect the ideas - a monad we write to or read from, without any additional constraints. I'd really love to have them split up: A more general class that has just 'tell' and 'writer', and another that also has 'listen' and 'pass' (perhaps also 'contained').
I tried to discuss it in haskell-cafe once <http://www.mail-archive.com/haskell-cafe <at> haskell.org/msg101756.html>, but without any feedback on the idea, so I thought everybody is happy with the current state.

In this case, it seems that nothing important would break by this change, because IMHO there are very very little custom instances of MonadWriter/Reader. I'm also a big fan of this proposal: http://hackage.haskell.org/trac/ghc/wiki/DefaultSuperclassInstances
If it were implemented and standardized, we could refactor type classes without breaking existing code (or breaking it just very little).
[In particular, it'd be possible to make Monad a subclass of Applicative, something I've always regretted we don't have.]

Other than that, I'm not sure how adding "contained" goes against this idea. If we split MonadWriter into two type classes, "contained" would simply go into the one with "listen" and "pass".

Best regards,
Petr

 

Also, the former can be done purely within the mtl, while the latter drags transformers into it.



-Edward

On Jan 27, 2013, at 3:30 PM, Petr P <petr.mvd <at> gmail.com> wrote:

  Dear maintainers,

I have two suggestions for MonadWriter:

(1) Remove the "Monoid w" constraint from the definition.

The constraints prevent creating new instances of the class that have only an implied monoid. For example, I needed to create a simple writer which always stores the last written element. I had to wrap it into Last, which was a nuisance for users of my library. Without the constraint, my instance would be quite simpler and still satisfying all the laws. There are many other similar use cases, like counting the number of written values (and disregarding their actual content) etc.

The constraint is meant to ensure that instances of that class obey the monad laws. But it's not the responsibility of a type class that its instances satisfy the laws. They could violate them even without this constraints. Instead, this constraint should be specified (and it is) in the definition of their instances.

It has been discussed in haskell-cafe <http://www.haskell.org/pipermail/haskell-cafe/2012-December/thread.html#105088> with arguments for and against.


(2) Add

  -- | <at> contained m <at> executes the action <at> m <at> in a contained environment and
  -- returns its value and its output. The current output is not modified.  
  contained :: m a -> m (a, w)

to MonadWriter.

This generalizes "pass" and "listen"  and has it's a sort of inverse to "writer" with these simple laws:

  writer <=< contained   = id
  contained . writer     = return

It seems as a understandable set of laws that its instances should obey.

It also expresses the same concept as "runWriterT" does, but inside the type class. In particular, for "WriterT" we have

  contained :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
  contained = lift . runWriterT

Current instances won't be affected as "contained" can be expressed using "pass" and "listen" (and vice versa).
Full details available at
[There "contained" is expressed without the "Monoid w" constraint as suggested in (1). If we keep the constraint, "contained" can be expressed more simply as
  containde k = pass (listen k >>= \x -> return (x, const mempty)).

Also, "contained" isn't probably a good name, I just couldn't think of anything better.]


   Best regards,
   Petr Pudlak

_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
Libraries <at> haskell.org
http://www.haskell.org/mailman/listinfo/libraries

Gmane