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.)
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
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With