Search code examples
haskelloption-type

How can I handle Nothing branch of maybe monad gracefully?


data Union = F Float | I Int | S String

getUnionFromString :: String -> Union
getUnionFromString str =
    case (readMaybe str :: Maybe Int) of
        Just i  -> I i
        Nothing ->
            case (readMaybe str :: Maybe Float) of
                Just f  -> F f
                Nothing -> S str

getStr (F f) = "Float " ++ show f
getStr (I i) = "Int " ++ show i
getStr (S s) = "String " ++ s

main = do
    str <- getLine
    putStrLn $ getStr (getUnionFromString str)

I want to make a function that transforms string to union type(Float | Int | String). So I used readMaybe to find out type of a string. And then, using case-of to handle Just and Nothing branch. I think it's not able to cope this situation with Monad behavior of Maybe, because I don't want to stop computation when Nothing got out, I want to handle Nothing case. Is there better way to write getUnionFromString function?


Solution

  • We can use Control.Applicative.<|> to simplify the logic of trying a different expression if one evaluates to Nothing:

    Prelude> import Control.Applicative
    Prelude Control.Applicative> Just 1 <|> Nothing
    Just 1
    Prelude Control.Applicative> Just 1 <|> Just 2
    Just 1
    Prelude Control.Applicative> Nothing <|> Just 1
    Just 1
    Prelude Control.Applicative> Nothing <|> Nothing
    Nothing
    

    Using this along with Data.Maybe.fromMaybe, we can simplify the code to:

    getUnionFromString :: String -> Union
    getUnionFromString str = fromMaybe (S str) $ (I <$> readMaybe str) <|> (F <$> readMaybe str)
    

    Haskell's type inference is able to figure out that the first readMaybe wants a Maybe Int and the second one wants Maybe Float, without us explicitly asking for it.

    Full code:

    import Control.Applicative
    import Data.Maybe (fromMaybe)
    import Text.Read
    
    data Union = F Float | I Int | S String
    
    getUnionFromString :: String -> Union
    getUnionFromString str = fromMaybe (S str) $ (I <$> readMaybe str) <|> (F <$> readMaybe str)
    
    getStr (F f) = "Float " ++ show f
    getStr (I i) = "Int " ++ show i
    getStr (S s) = "String " ++ s
    
    main = do
      str <- getLine
      putStrLn $ getStr (getUnionFromString str)