[Haskell-cafe] ANNOUNCE: error-message

Gregory Crosswhite gcross at phys.washington.edu
Sat Dec 5 17:13:10 EST 2009


Recall that the definition of liftM2 is

==================================================
liftM2  :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 f m1 m2          = do { x1 <- m1; x2 <- m2; return (f x1 x2) }
==================================================

which, if I understand correctly, desugars to

==================================================
liftM2 f m1 m2 =
	m1 >>=
	(
		\x1 ->
		m2 >>=
		(	
			\x2 ->
			return (f x1 x2)
	)
)
==================================================

The problem comes from the fact that >>= takes a *function* as its second argument, and so if the first argument is an error then we can't evaluate the second argument in order to see if it has an error as well.  For example, consider the following script:

===================================================
import Control.Applicative
import Control.Monad

import Data.ErrorMessage

newtype E a = E (Either String a)


instance Functor E where
   fmap _ (E (Left error)) = E (Left error)
   fmap f (E (Right argument)) = E (Right (f argument))

instance Applicative E where
   pure = E . Right
   (<*>) (E (Left error2)) (E (Left error1)) = E (Left (error1 ++ error2))
   (<*>) (E (Left error)) _ = E (Left error)
   (<*>) _ (E (Left error)) = E (Left error)
   (<*>) (E (Right function)) (E (Right argument)) = E (Right (function argument))

instance Monad E where
  return        = E . Right
  E (Left  l) >>= _ = E (Left l)
  E (Right r) >>= f = f r
  fail msg      = E (Left msg)

sum_using_monad :: Either String Int
(E sum_using_monad) = (liftM2 (+)) (E (Left "A")) (E (Left "B"))

sum_using_applicative :: Either String Int
(E sum_using_applicative) = (liftA2 (+)) (E (Left "A")) (E (Left "B"))

main = do
 putStrLn . show $ sum_using_monad
 putStrLn . show $ sum_using_applicative
===================================================

(Sorry about all of the annoying E's;  I needed to do this in order to override the instance declaration for Either String.)

Run this script and you will see:

Left "A"
Left "BA"

Thus, the difference in the semantics is inherent from the way that >>= and liftM2 are defined.  The only way that I can think to get around this is change the definition of >>= so that if the first argument is an error then it calls the second argument with "undefined";  if this returns a (Left error) then combine the two errors, and if it returns anything else or throws an exception (e.g. Prelude.undefined) then ignore it and just return the first argument.

Cheers,
Greg

On Dec 5, 2009, at 1:28 PM, Brent Yorgey wrote:

> On Thu, Dec 03, 2009 at 01:50:06PM -0800, Gregory Crosswhite wrote:
>> 
>> Or, even more concisely:
>> 
>> ==================================================
>>    sumWithError_3 = liftM2 (+)
>> ==================================================
>> 
>> Unfortunately though, neither of these definitions have the same semantics as the original @sumWithError@, as using both we get the following error message for @showSumOrErrorOf (-1) (-2)@:
>> 
>> ==================================================
>>    Error computing the square root of -1:
>>        Square roots cannot be taken of negative numbers.
>> ==================================================
>> 
>> That is, we have lost the second of the two error messages.  The reason for this is that 'Monad'-style error processing expresses the computation as a sequence, and gives up as soon as it sees any error.  In this case of @sumWithError@, however, the evaluation of the second argument can proceed even if there was an error in the first argument.  Thus, rather than using a 'Monad' pattern, we use an 'Applicative' pattern:
>> 
>> ==================================================
>>    sumWithError_4 = liftA2 (+)
>> ==================================================
>> 
>> Now both error messages are displayed.
> 
> I see no inherent reason that liftM2 (+) cannot collect both error
> messages.  No one says that "monad-style error processing" *must* stop
> as soon as it sees an error. And having different semantics for liftA2
> and liftM2 (etc.)  is strange at best.  They ought to be equivalent
> for any type constructor with both Monad and Applicative instances.
> 
> -Brent
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe at haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe



More information about the Haskell-Cafe mailing list