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 w开发者_开发技巧ant 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.
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
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
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
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
精彩评论