8

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.

Viktor Dahl
  • 1,942
  • 3
  • 25
  • 36

4 Answers4

12

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
Landei
  • 54,104
  • 13
  • 100
  • 195
5

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
Ganesh Sittampalam
  • 28,821
  • 4
  • 79
  • 98
  • 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. – Rotsor Jun 09 '11 at 12:51
  • 2
    Can you give more details on the change, specifically what changed and when? – mightybyte Jun 09 '11 at 13:11
  • 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. – Antoine Latter Jun 09 '11 at 19:07
  • 1
    @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 Jun 09 '11 at 20:18
  • 2
    @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 Jun 09 '11 at 20:25
  • @mightybyte: As to when, about a year or so back, we had the discussion on the libraries mailing list. – Edward Kmett Jun 09 '11 at 20:26
4

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
Community
  • 1
  • 1
Alessandro Vermeulen
  • 1,321
  • 1
  • 9
  • 28
1

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
Edward Kmett
  • 29,632
  • 7
  • 85
  • 107