Using 'Either' in Haskell

12,885

Solution 1

Why monads?

test p (Right t1) (Right t2) | p t1 && p t2 = Right t1
                             | otherwise = Left "nope"
test _ (Left t1) _ = Left t1
test _ _ (Left t2) = Left t2

Solution 2

If you do want to do it with a Monad it would look something like this, but the Monad instance for Either was recently changed so that this won't actually work in recent GHCs:

do v1 <- t1
   v2 <- t2
   guard (p v1 && p v2) `mplus` Left someString
   return v1

Solution 3

You could create your own Error datatype and make it instance of Monad.

data Computation a = Error String | Result a


instance Monad Computation where
    (Result x)  >>= k   =  k x
  e@(Error a)   >>= k   =  e

And then use the method described by Ganesh Sittampalam. (You will need to add an instance MonadPlus Computation too.

Update for completeness it would look like this:

import Control.Monad

data Computation a = Error String | Result a



instance Monad Computation where
  return a = Result a
  (Result x)  >>= k   =  k x
  (Error a)   >>= k   =  Error a

instance MonadPlus Computation where
  mzero              = Error "Always fail"
  mplus (Error a) r  = r
  mplus l         _  = l


check :: (Int -> Bool) -> Computation Int  
check p =   do v1 <- Result 4
               v2 <- Result 2
               guard (p v1 && p v2) `mplus` Error "someString"
               return v1

Solution 4

You can separate out the monadic action from the propagation of Left values if you really want to:

import Control.Monad
import Control.Applicative
import Control.Monad.Instances

This yields the simple monadic action:

foo :: Type -> Type -> Either String Type
foo t1 t2 | p t1 && p t2 = Right t1
          | otherwise    = Left somestring

Which you can apply to monadic arguments to get the function you want, using

fooM :: Either String Type -> Either String Type -> Either String Type
fooM t1 t2 = join (foo <$> t1 <*> t2)

or equivalently

fooM t1 t2 = do
    a <- t1
    b <- t2
    foo a b
Share:
12,885
Viktor Dahl
Author by

Viktor Dahl

Github account

Updated on July 31, 2022

Comments

  • Viktor Dahl
    Viktor Dahl almost 2 years

    I have two values, t1 and t2, of type Either String Type. The Left-value is used for error handling. These values are used in a function which returns Either String Type.

    What I want to do is check if both t1 and t2 are Right-values and satisfy p :: Type -> Bool. If they do, I want to return Right (the type inside t1). If both t1 and t2 are Right-values, but do not satisfy p, I want to return Left someString. If one of t1 or t2 is a Left value, I just want to pass on that value.

    How can I do this in an elegant way? I have a hunch that using Either as a monad is the right thing to do, but I'm not sure of how to go about it.

  • Bayquiri
    Bayquiri almost 13 years
    It does work with GHC 7.0.3 if I import Control.Monad.Error from mtl package. One can also complete abstraction from Either by replacing Left with throwError . strMsg. Not that it's terribly useful though.
  • mightybyte
    mightybyte almost 13 years
    Can you give more details on the change, specifically what changed and when?
  • Tsuyoshi Ito
    Tsuyoshi Ito almost 13 years
    Your definition of mplus does not do what you want it to do.
  • Alessandro Vermeulen
    Alessandro Vermeulen almost 13 years
    @Tsuyoshi Ito, I fxied the code so mplus shows proper behaviour.
  • Antoine Latter
    Antoine Latter almost 13 years
    mightybyte: the only change I know of is that 'fail' no longer maps to 'Left'. I'm not sure how that affects the above example, though.
  • Edward Kmett
    Edward Kmett almost 13 years
    @Antoine Latter: guard False = mzero, but Either a is not an instance of MonadPlus as the instance is defined in Control.Monad.Instances and does not have access to the Error class from the Control.Monad.Trans.Error which is out in transformers. mtl then re-exports the instance via Control.Monad.Error.
  • Edward Kmett
    Edward Kmett almost 13 years
    @mightybyte: We moved the instance for Either from transformers (and before that mtl) into base, but as the instance involves both a class and a type from the Prelude and isn't stated by any current Haskell standard, we had to export it from Control.Monad.Instances rather than just put it into scope everywhere. In the process we removed the assumption of Error from the left hand argument to Either. This had the side-effect of changing the behavior of fail, from Left . strMsg to error. We left MonadPlus and Alternative in transformers.
  • Edward Kmett
    Edward Kmett almost 13 years
    @mightybyte: As to when, about a year or so back, we had the discussion on the libraries mailing list.