As you can see, I wrote program, e.g:
test "12 124 212" = Right [12, 124, 212]
test "43 243 fs3d 2" = Left "fs3d is not a number"
test :: String -> Either String [Int]
test w = iter [] $ words w
where
iter acc [] = Right (reverse acc)
iter acc (x:xs) = if (all isDigit x) then
iter ((read x):acc) xs
else
Left (x++ "is not a number")
A am starting learning monads. Could you show me how to implement it using monads ?
2 Answers 2
I think you are looking for traverse/mapM (they're the same for lists). Also you can use readEither for simplification:
import Data.Traversable (traverse)
import Data.Bifunctor (first)
import Text.Read (readEither)
test :: String -> Either String [Int]
test = traverse parseItem . words
parseItem :: String -> Either String Int
parseItem x = first (const $ x++" is not a number") $ readEither x
So what does mapM do? It basically implements the recursion over the list that you did manually. However, unlike the standard map function it takes a monadic function (parseItem in our case, where Either String is a monad) and applies one step on the list after the other:
iter [] = Right []
iter (x:xs) = do
r <- parseItem x
rs <- iter xs
return (r:rs)
10 Comments
Left value, but apparently it gets its own error messages. Well, we can fix that as well...traverse?do notation in the end of the answer. I think it's a good excercise to convert that to >>= yourself :-) Notice I've left out words there for simplicityBergi's answer is just right, but maybe you'll find it easy to understand presented this way:
test :: String -> Either String [Int]
test str = traverse parseNumber (words str)
parseNumber :: String -> Either String Int
parseNumber str
| all isDigit str = Right (read str)
| otherwise = Left (str ++ " is not a number")
The other thing I'd recommend is don't write tail-recursive accumulator loops like iter in your example. Instead, look at library documentation and try to find list functions that do what you want. In this case, as Bergi correctly pointed out, traverse is exactly what you want. It will take some study to get fully comfortable with this function, though. But given how the Monad instance of Either and the Traversable instance of lists work, the traverse in this example works like this:
-- This is the same as `traverse` for lists and `Either`
traverseListWithEither :: (a -> Either err b) -> [a] -> Either err [b]
traverseListWithEither f [] = Right []
traverseListWithEither f (a:as) =
case f a of
Left err -> Left err
Right b -> mapEither (b:) (traverseListWithEither f as)
-- This is the same as the `fmap` function for `Either`
mapEither :: (a -> b) -> Either e a -> Either e b
mapEither f (Left e) = Left e
mapEither f (Right a) = Right (f a)