3

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 ?

Bergi
671k162 gold badges1k silver badges1.5k bronze badges
asked Mar 31, 2016 at 19:06

2 Answers 2

4

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)
answered Mar 31, 2016 at 19:25
Sign up to request clarification or add additional context in comments.

10 Comments

Why you aplicate two arguments for readEither ? It takes only one (String) argument
@HaskellFun: Thanks. I somehow expected it to take the error message and the string to be parsed. Fixed now using the amazing Bifunctors :-)
@HaskellFun: Oh my, I thought it would return the input string in the Left value, but apparently it gets its own error messages. Well, we can fix that as well...
@HaskellFun: Which part, the traverse?
@HaskellFun: See the version with the 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 simplicity
|
2

Bergi'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)
answered Apr 1, 2016 at 0:23

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.