I’m trying to write a function that takes some input x and returns an Either. This function tries to apply three functions f, g, h in that order to the input x, and returns the first successful (= Right) result. If everything fails, it returns Left. How do I do that?
Here’s a concrete example. These are our three functions f, g and h:
halveIfEight :: Int -> Either String Int
halveIfEight 8 = Right 4
halveIfEight x = Left "not eight"
halveIfEven :: Int -> Either String Int
halveIfEven x | x `mod` 2 == 0 = Right $ x `div` 2
| otherwise = Left "not even"
halveIfPositive :: Int -> Either String Int
halveIfPositive x | x > 0 = Right $ x `div `2
| otherwise = Left "not positive"
In the code below, halve is the function in question:
halve :: Int -> Either String Int
halve x =
case halveIfEight x of
Right y -> Right y
Left err1 ->
case halveIfEven x of
Right y -> Right y
Left err2 ->
case halveIfPositive x of
Right y -> Right y
Left err3 -> Left $ err1 ++ ", " ++ err2 ++ " and " ++ err3
main :: IO ()
main = do
putStrLn $ show $ halve 8 -- Right 4
putStrLn $ show $ halve 6 -- Right 3
putStrLn $ show $ halve (-4) -- Right (-2)
putStrLn $ show $ halve (-3) -- Left "not eight, not even and not positive"
halve behaves correctly, but I feel like there’s too much nesting. And my implementation doesn’t generalize to the case when I need to try indefinitely many sub-operations. Is there a cleaner/more idiomatic way to achieve what I want here?
I think you can just do simple recursion with an accumulator here:
go acc [] = Left (reverse acc)
go acc (Right y : _) = Right y
go acc (Left e : rs) = go (e:acc) rs
And in the end you either pretty print your errors or return the right side:
bimap pretty id (go [] results)
This effectively says “if Left apply the first function otherwise apply the first”.
In full this would look something like:
import Data.Functor
import Data.List (intercalate)
halve :: Int -> Either String Int
halve x = bimap pretty id (go [] results)
where
fns = [halveIfEight, halveIfEven, halveIfPositive]
results = zipWith ($) fns (cycle [x])
go acc [] = Left (reverse acc)
go acc (Right y : _) = Right y
go acc (Left e : rs) = go (e:acc) rs
pretty errs =
case errs of
[] -> ""
[e] -> e
_ -> intercalate ", " (init errs) <> " and " <> last errs
I like this idea! I tried something similar with foldl:
halve :: Int -> Either String Int
halve x =
let
fs = [ halveIfEight, halveIfEven, halveIfPositive ]
folder (Just (Right x)) _f = Just $ Right x
folder _res f = Just $ f x
in
fromJust $ foldl folder Nothing fs
The more idiomatic way would be to use the MonadError instance of Either
halve :: Int -> Either String Int
halve x =
halveIfEight x `catchError` \ err1 ->
halveIfEven x `catchError` \ err2 ->
halveIfPositive x `catchError` \ err3 ->
Left $ err1 ++ ", " ++ err2 ++ " and " ++ err3
The (Monad m, Monoid e) => Alternative (ExceptT e m) instance of the Except type from transformers does what you want. Either the first success, or the collection of errors. You need to first put the error in each Left into a Monoid, though:
Either doesn’t have a similar Alternative instance, but it could be easily defined on a newtype. The existing Semigroup instance for Either does almost what you want, except that it only retains the last error, which seems less useful than retaining all the erros:
ghci> Left @[String] @Int ["foo"] <> Left ["bar"]
Left ["bar"]
ghci> Left @[String] @Int ["foo"] <> Right 5 <> Left ["bar"]
Right 5
ghci> Left @[String] @Int ["foo"] <> Right 5 <> Right 6 <> Left ["bar"]
Right 5
It seems like you want exactly the swapped semantics of Either, no?
halveIfEight :: Int → Either Int String
halveIfEight 8 = Left 4
halveIfEight x = Right “not eight”
halveIfEven :: Int → Either Int String
halveIfEven x | x mod 2 == 0 = Left $ x div 2
| otherwise = Right “not even”
halveIfPositive :: Int → Either Int String
halveIfPositive x | x > 0 = Left $ x div 2
| otherwise = Right “not positive”
halve :: Int → Either Int String
halve x = do
err1 ← halveIfEight x
err2 ← halveIfEven x
err3 ← halveIfPositive x
return (err1 ++ ", " ++ err2 ++ " and " ++ err3)
You could even use something like mapM or similar, as suggested by @augustss
halve :: Int -> Either Int String
halve x = intercalate ", "
<$> sequenceA [halveIfEight x, halveIfEven x, halveIfPositive x]
I know that it might seem unnatural to put the “error” case in the Right constructor, but semantically here you’re just looking for short-circuiting on an answer, with accumulation in the error case, which is best matched by Either answer error (in my opinion).
There was a discussion about this on r/Haskell recently, about how Either should’ve been called Error etc. I think this is a great example of why Either Monad’s Left-Right asymmetry shouldn’t be conflated with error propagation, it does what it does and sometimes it happens to be convenient for error handling…
For an early return (short-circuit) on an initial segment of a list, the correct primitive is foldr rather than foldl. With a non-empty input foldr1 is sometimes a more natural choice:
λ> import Data.Bifunctor (first)
λ> erradd r e = first (((e ++ ", ") ++)) r
λ> vals = [Left "foo", Left "bar", Left "baz"]
λ> foldr1 (\x r -> either (erradd r) Right x) vals
Left "foo, bar, baz"
λ> vals = [Left "foo", Right 42, Left "baz"]
λ> foldr1 (\x r -> either (erradd r) Right x) vals
Right 42
Though in this case, I’d go with a list of error strings, rather than a concatenated error string, in whcih case foldr is more natural:
λ> erradd r e = first (e :) r
λ> op x r = either (erradd r) Right x
λ> valsr = [Left "foo", Right 42, Left "baz"]
λ> valsl = [Left "foo", Left "bar", Left "baz"]
λ> foldr op (Left []) valsr
Right 42
λ> foldr op (Left []) valsl
Left ["foo","bar","baz"]