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 Just
s, 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 Applicative
s, 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