In the previous lectures we saw the >> and >>= operators and the return function used with IO. It turns out that not only IO but a lot of other types with the same form support these operators.
Let's have a look at the type of these operators in case of the IO type.
(>>=) :: IO a -> (a -> IO b) -> IO b
(>>) :: IO a -> IO b -> IO b
return :: a -> IO a
If we replace IO with Maybe, we get the following results.
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
(>>) :: Maybe a -> Maybe b -> Maybe b
return :: a -> Maybe a
The >> operator is not that interesting. What about the >>= operator? Maybe is an ideal type to represent actions that might fail. The following pattern is used frequently in imperative programming.
def validateAges(listAsString):
intList = parseStringList(listAsString)
if not intList:
return None
ret = mean(intList) # might raise exception
if ret > 400:
return None
if ret < 0:
return None
return ret
The previous code looks like this in Haskell:
import Text.Read
safeMean :: [Int] -> Maybe Int
safeMean [] = Nothing
safeMean lst = Just ((sum lst) `div` (length lst))
data Age = Age Int deriving (Eq, Show)
toAge :: Int -> Maybe Age
toAge age
| age < 0 = Nothing
| age > 400 = Nothing
| otherwise = Just (Age age)
validateMeanAge :: String -> Maybe Age
validateMeanAge str = case (readMaybe str) :: Maybe [Int] of
Nothing -> Nothing
Just lst -> case safeMean lst of
Nothing -> Nothing
Just mean -> toAge mean
This looks terrible. Although if you have a look at the type of each case expression an interesting pattern emerges. Maybe a -> (a -> Maybe b) -> Maybe b which is exactly the same as the type of >>=.
validateMeanAge :: String -> Maybe Age
validateMeanAge str = (readMaybe str) >>= safeMean >>= toAge
This is beautiful. I can not imagine a more concise description of the problem with the tools that we have now.
- Since all of the necessary operators are implemented for Maybe, you can use do notation. Rewrite validateMeanAge using do notation.