Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maybe constructing data from Maybes

Tags:

haskell

I have a type

data Foo = Foo { bar :: Bar, baz :: Baz }

and I am trying to parse a file to construct a Foo. I have functions to try parsing the Bar and Baz members

parseBar :: String -> Maybe Bar
parseBar line = ...

parseBaz :: String -> Maybe Baz
parseBaz line = ...

that may fail, and a function that maybe constructs the Foo from the results

parseFoo :: String -> Maybe Foo
parseFoo line = let bar = parseBar line
                    baz = parseBaz line
                in parseFoo' bar baz

parseFoo' :: Maybe Bar -> Maybe Baz -> Maybe Foo
parseFoo' (Just bar) (Just baz) = Just Foo { bar=bar baz=baz }
parseFoo' _ _ = Nothing

This gets the job done, but defining the extra function just to do the pattern matching seems clumsy. Is there a cleaner way to either "unbox" the Maybe Bar and Maybe Baz or return Nothing? Am I going about this all wrong? (I'm still working my way up to monads. I just really wanted to actually write something now that I've made it to Hello, World nine chapters in.)

like image 641
xuxian Avatar asked Jun 09 '20 22:06

xuxian


3 Answers

Luckily you don't need the full power of monads for this. Applicative functors are all you need:

parseFoo line = Foo <$> parseBar line <*> parseBaz line
like image 107
Robin Zigmond Avatar answered Nov 06 '22 05:11

Robin Zigmond


This is what the Monad instance of Maybe is all about. You can thus calculate your Maybe Foo with:

parseFoo :: String -> Maybe Foo
parseFoo line = do
    bar <- parseBar line
    baz <- parseBaz line
    parseFoo' bar baz

Here bar and baz are not Maybe Bar and Maybe Baz objects, but Bar and Baz objects. You thus can define a parseFoo with:

parseFoo' :: Bar -> Baz -> Maybe Foo
parseFoo' bar baz = Just Foo { bar=bar baz=baz }

Here from the moment a computation returns a Nothing, it means that the result is a Nothing, so only if the parseBar line returns a Just …, and the parseBaz returns a Just …, it will thus return the result of parseFoo bar baz.

This is because the Monad instance of Maybe is implemented as:

instance Monad Maybe where
    return = Just
    Nothing >>= _ = Nothing
    Just x >>= f = f x

A do block is syntactical sugar, so the above do block is converted to:

parseFoo line = parseBar line >>= \bar -> (parseBaz line >>= \baz -> parseFoo bar baz)

If parseFoo always returns Just in case it retrieves two Justs, we can implement this as:

parseFoo' :: Bar -> Baz -> Foo
parseFoo' bar baz = Foo { bar=bar baz=baz }

In that case we can make ues of (<$>) :: Functor f => (a -> b) -> f a -> f b and (<*>) :: Applicative f => f (a -> b) -> f a -> f b to process the data:

parseFoo :: String -> Maybe Foo
parseFoo line = parseFoo' <$> parseBar line <*> parseBaz line

In this specific case, parseFoo is however semantically the same as Foo, so we do not need a parseFoo, and can work with:

parseFoo :: String -> Maybe Foo
parseFoo line = Foo <$> parseBar line <*> parseBaz line
like image 8
Willem Van Onsem Avatar answered Nov 06 '22 07:11

Willem Van Onsem


Before Applicatives, you still can do

parseFoo :: String -> Maybe Foo
parseFoo line = 
  case (parseBar line, parseBaz line) of
    (Just bar, Just baz) -> Just Foo { bar=bar baz=baz }
    _ -> Nothing

and avoid defining the parseFoo' function.

like image 3
Alexey Romanov Avatar answered Nov 06 '22 05:11

Alexey Romanov