Re: [Haskell] Types of when and unless in Control.Monad

On 06/04/2012 08:47 PM, Christian Höner zu Siederdissen wrote:
Hi Andreas,
I think you missed our point. Say:
gearDown :: IO Bool landPlane :: IO ()
do g<- gearDown if g then landPlane else error "we're all gonna die, bail out"
But now, I am really lazy (in a bad way)
do when gearDown landPlane
You probably meant to write do gearDown landPlane (Since 'when' expects a pure Bool, the code you wrote does not type-check.) Well, you can ignore results in a 'do', but this has nothing to do with 'when'. There is a ghc flag -fwarn-unused-do-bind that prevents you from ignoring results silently, in this case you would have to write do _ <- gearDown landPlane to make it obvious that you ignore a result of a monadic computation. Maybe you prefer this style, but I would like the freedom to silently ignore unneeded results.
I can not now accidentally land my plane if my gear ist stuck in "up" (returned false). If "when" suppresses the return value, the compiler does not warn you if you use "when" together with a function that returns something important.
It seems you have confused condition- and then-part of 'when'.
My point is /not/ that I want the result of "when", but that I want to know that I am currently suppressing the result of the inner action, which could be disastrous.
And simply adding "when_" is the safest (one of the safer?) way of dealing with your wish.
The reason to have a mapM_ additionally to mapM is that in some cases, e.g. as last statement of a do block of type m (), mapM does not type check. However, generalizing the type of 'when' does not break any programs, and then a when_ is superfluous. More in an answer to Henning... Cheers, Andreas
* Andreas Abel
[04.06.2012 20:36]: On 04/22/2012 01:49 PM, Ivan Lazar Miljenovic wrote:
On 22 April 2012 21:39, Christian Höner zu Siederdissen
wrote: * Julian Gilbey
[22.04.2012 09:22]: On Sat, Apr 21, 2012 at 08:28:27PM -0500, Strake wrote:
On 21/04/2012, Andreas Abel
wrote: > to avoid silly "return ()" statements like in > > when cond $ do > monadicComputationWhoseResultIWantToDiscard > return () (when cond ∘ void) monadicComputationWhoseResultIWantToDiscard or when cond $ ()<$ monadicComputationWhoseResultIWantToDiscard
How is that simpler than
when cond monadicComputationWhoseResultIWantToDiscard
which it would be with the suggested new type?
Julian
Wouldn't "when_" and "unless_" or similar be better? I'd probably like to have the compiler annoy me, since it is not clear that I want to discard the result. If I really want to discard, I should have to make it clear as there is probably a good reason for the inner function to return a result in the first place?
Agreed; I'm not sure if I agree with having such functionality (Henning makes some good points), but if people deem it desirable then I think it would be better to have them with new names for the reasons you state.
Mmh, this discussion has cooled down, but I just found your answers which had been stashed away by my mail agent, and I feel I have to reply...
Concerning the suggestion that when_ would be in sync with forM_ and whenM_ I'd say: not really. forM_ and whenM_ discard the result of the monadic computation, while when and when_ do not even have such a result. They always just perform some monadic effect and return nothing.
Repeating myself, 'when' can never have a result, since it is an if-then without an else. Thus, it is a proper command; and if you want to have a conditional monadic computation which does return a result, you can simply not use 'when' or 'unless', logic forces you to use 'if' or 'ifM'.
I do not understand the worries that one could accidentially use 'when' with a monadic computation whose result one actually cares for. If that was the intention of the library designers, they should have given many other function a more specific type, most prominently
:: m () -> m b -> b
That would have ensured that you cannot discard the result of the first computation by accident. But would you want to work with this? My answer is no.
Other types that would be changed to implement this kind of safety policy are:
mapM_ :: (a -> m ()) -> [a] -> m () forM_ :: [a] -> (a -> m ()) -> m () sequence_ :: [m ()] -> m () forever :: m () -> m ()
and many more, like zipWithM_, foldM_, replicateM_.
Sorry, but I think all these function have been given their maximal general type
==> to be able to ignore a result of a monadic computation
==> without further noise.
In my opinion, the types of when and unless are not general enough, an that is, imho, just an accident of history. Because it is the type that inferred for the shortest definition, which is
when cond m = if cond then m else return ()
Please reevaluate my proposal to change to
when :: Bool -> m a -> m () unless :: Bool -> m a -> m ()
in the light of the above arguments.
Cheers, Andreas
-- Andreas Abel<>< Du bist der geliebte Mensch.
Theoretical Computer Science, University of Munich Oettingenstr. 67, D-80538 Munich, GERMANY
andreas.abel@ifi.lmu.de http://www2.tcs.ifi.lmu.de/~abel/
-- Andreas Abel <>< Du bist der geliebte Mensch. Theoretical Computer Science, University of Munich Oettingenstr. 67, D-80538 Munich, GERMANY andreas.abel@ifi.lmu.de http://www2.tcs.ifi.lmu.de/~abel/

On Tue, 5 Jun 2012, Andreas Abel wrote:
The reason to have a mapM_ additionally to mapM is that in some cases, e.g. as last statement of a do block of type m (), mapM does not type check.
The functions mapM and mapM_ are really distinct. That's why they are generalized in different modules: mapM_ is generalized in Foldable mapM is generalized in Traversable Using mapM instead of mapM_ can lead to a memory leak and I think the confusion between mapM and mapM_ was the initial reason to add the unused-binding warning to GHC. And it actually helped me spotting a lot of calls to mapM that have to be calls to mapM_.

Using mapM instead of mapM_ can lead to a memory leak and I think the confusion between mapM and mapM_ was the initial reason to add the unused-binding warning to GHC. And it actually helped me spotting a lot of calls to mapM that have to be calls to mapM_.
The different space behaviour is a very good reason to make the distinction for mapM and mapM_. However, it's not a good reason to choose arbitrarily restrictive types for perfectly generic functions in other cases IMHO. At least I don't see that there's a performance reason not to generalize when or unless, as their implementation would stay the same. I'd argue that making functions more restrictive than necessary isn't an indication of safety, it's just confusing :) Cheers, Andres -- Andres Löh, Haskell Consultant Well-Typed LLP, http://www.well-typed.com

On Tue, 5 Jun 2012, Andres Löh wrote:
Using mapM instead of mapM_ can lead to a memory leak and I think the confusion between mapM and mapM_ was the initial reason to add the unused-binding warning to GHC. And it actually helped me spotting a lot of calls to mapM that have to be calls to mapM_.
The different space behaviour is a very good reason to make the distinction for mapM and mapM_. However, it's not a good reason to choose arbitrarily restrictive types for perfectly generic functions in other cases IMHO. At least I don't see that there's a performance reason not to generalize when or unless, as their implementation would stay the same.
The type of 'when' is not arbitrarily restrictive. It makes sure, that no monadic result is ignored by accident. Btw. the mapM memory leak can be easily and accidentally resurrected by writing when_ b (mapM f xs) with f :: a -> m () when_ :: Bool -> m a -> m ()

Btw. the mapM memory leak can be easily and accidentally resurrected by writing
when_ b (mapM f xs)
with
f :: a -> m () when_ :: Bool -> m a -> m ()
I don't understand the whole mapM analogy (yet). I don't need when_ to get into trouble with using mapM. Also, whether a space leak with mapM arises or not typically doesn't depend on whether you use its result or not. It's the other way round: if you run mapM on a huge list but aren't interested in the results, then you should use mapM_ instead. So the problem here is accidentally producing a (large) result rather than accidentally ignoring it. Cheers, Andres -- Andres Löh, Haskell Consultant Well-Typed LLP, http://www.well-typed.com

Hello,
-1 for me too, for the reasons already discussed in the thread (i.e.,
because it makes it easy to accidentally ignore potentially important
results). I think that gains in readability from this generalization are
at best questionable.
-Iavor
On Tue, Jun 5, 2012 at 2:07 PM, Andres Löh
Btw. the mapM memory leak can be easily and accidentally resurrected by writing
when_ b (mapM f xs)
with
f :: a -> m () when_ :: Bool -> m a -> m ()
I don't understand the whole mapM analogy (yet). I don't need when_ to get into trouble with using mapM. Also, whether a space leak with mapM arises or not typically doesn't depend on whether you use its result or not. It's the other way round: if you run mapM on a huge list but aren't interested in the results, then you should use mapM_ instead. So the problem here is accidentally producing a (large) result rather than accidentally ignoring it.
Cheers, Andres
-- Andres Löh, Haskell Consultant Well-Typed LLP, http://www.well-typed.com
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

On Tue, 5 Jun 2012, Andres Löh wrote:
Btw. the mapM memory leak can be easily and accidentally resurrected by writing
when_ b (mapM f xs)
with
f :: a -> m () when_ :: Bool -> m a -> m ()
I don't understand the whole mapM analogy (yet). I don't need when_ to get into trouble with using mapM.
If I write when b (mapM f xs) with f :: a -> m () when :: Bool -> m () -> m () then GHC will say something like: Cannot match [()] (result of 'mapM') with () (argument of 'when').

On 06.06.12 12:13 AM, Henning Thielemann wrote:
On Tue, 5 Jun 2012, Andres Löh wrote:
Btw. the mapM memory leak can be easily and accidentally resurrected by writing
when_ b (mapM f xs)
with
f :: a -> m () when_ :: Bool -> m a -> m ()
I don't understand the whole mapM analogy (yet). I don't need when_ to get into trouble with using mapM.
If I write
when b (mapM f xs)
with
f :: a -> m () when :: Bool -> m () -> m ()
then GHC will say something like: Cannot match [()] (result of 'mapM') with () (argument of 'when').
Well, but nothing in the type system prevents you from writing mapM f xs >> cont or do mapM f xs cont so you are not warned of a space leak in the common situations anyway, but you are responsible yourself. I don't know why 'when' should be any different than '>>'. Cheers, Andreas -- Andreas Abel <>< Du bist der geliebte Mensch. Theoretical Computer Science, University of Munich Oettingenstr. 67, D-80538 Munich, GERMANY andreas.abel@ifi.lmu.de http://www2.tcs.ifi.lmu.de/~abel/

On Wed, 6 Jun 2012, Andreas Abel wrote:
Well, but nothing in the type system prevents you from writing
mapM f xs >> cont
or
do mapM f xs cont
so you are not warned of a space leak in the common situations anyway, but you are responsible yourself.
I don't know why 'when' should be any different than '>>'.
That's why I said that I also prefer (>>) :: m () -> m a -> m a

Henning Thielemann wrote:
The type of 'when' is not arbitrarily restrictive. It makes sure, that no monadic result is ignored by accident.
I completely agree with this point. If 'when' had a more relaxed type signature, then the combinator would be slightly more convenient to use, but it would also be more dangerous to use. Personally, I prefer to err on the side of caution. Besides, it's not my impression that 'when' is particularly inconvenient to use as it is now, especially since the introduction of 'void'. Just my 2 cents. Peter

On 06.06.2012 16:29, Peter Simons wrote:
Henning Thielemann wrote:
The type of 'when' is not arbitrarily restrictive. It makes sure, that no monadic result is ignored by accident.
I completely agree with this point. If 'when' had a more relaxed type signature, then the combinator would be slightly more convenient to use, but it would also be more dangerous to use. Personally, I prefer to err on the side of caution. Besides, it's not my impression that 'when' is particularly inconvenient to use as it is now, especially since the introduction of 'void'.
Just my 2 cents.
That seems to be the opinion of the majority of those who have answered to my proposal. Which shall henceforth be considered void. Cheers, Andreas -- Andreas Abel <>< Du bist der geliebte Mensch. Theoretical Computer Science, University of Munich Oettingenstr. 67, D-80538 Munich, GERMANY andreas.abel@ifi.lmu.de http://www2.tcs.ifi.lmu.de/~abel/
participants (5)
-
Andreas Abel
-
Andres Löh
-
Henning Thielemann
-
Iavor Diatchki
-
Peter Simons