Question

I often have a code that has "maybe Nothing someFunc" pattern:

instance FromJSON SaveSection where
  parseJSON (Object o) =
      SaveSection <$>
      o .:? "eventId" <*>
      (maybe Nothing parseUSDate <$> o .:? "eventDate") <*>
      o .:? "eventRecId" <*>
      o .:? "idxId" <*>
      (maybe Nothing parseUSDate <$> o .:? "idxDate") <*>
      o .:? "idxRecId"

Here parseUSDate has type Text -> Maybe Date.

Aeson parsing obviously returns Maybe Text.

So it looks to me that I need to lift through 2 layers of Maybe here. And I have no clue how to do it any other way but with maybe Nothing someFunc pattern.

Am I missing some obvious "flatten" or whatever function that I could use here?

EDIT: Thanks for Alexey's answer.

This is exactly what i was looking for. Here's the end result:

instance FromJSON SaveSection where
  parseJSON (Object o) =
      SaveSection <$>
      o .:? "eventId" <*>
      ((>>= parseUSDate) <$> o .:? "eventDate") <*>
      o .:? "eventRecId" <*>
      o .:? "idxId" <*>
      ((>>= parseUSDate) <$> o .:? "idxDate") <*>
      o .:? "idxRecId"
Was it helpful?

Solution 2

If you have f' = maybe Nothing f, then the types have to be f :: a -> Maybe b and f' :: Maybe a -> Maybe b (where a and b may be variables or specific types), or it won't type-check. But this is just the type of >>= for the Maybe monad: Maybe a -> (a -> Maybe b) -> Maybe b! So maybe Nothing f can be written as (>>= f).

OTHER TIPS

There's the ever-handy Control.Monad.join function:

> join (Just (Just 1))
Just 1
> join (Just Nothing)
Nothing
> join Nothing
Nothing

I'm not an expert on Aeson, but if I do:

> :m Control.Monad Control.Applicative Data.Aeson Data.Text
> :set -XOverloadedStrings
> :set +m 
> let f :: Text -> Maybe Text
|     f = Just    -- Stand-in for parseUSDate
> :t \o -> join <$> liftM f <$> o .:? "key"
Object -> Parser (Maybe Text)
> -- Has the same type as your expression
> :t \o -> maybe Nothing f <$> o .:? "key"
Object -> Parser (Maybe Text)

Is that the sort of thing you're looking for?

EDIT: Fixed so that it actually works... My initial generic f :: a -> Maybe a was messing things up.


You could make an operator to clean this up:

infixl 9
(>>=$) :: (Functor f, Monad m) => f (m a) -> (a -> m b) -> f (m b)
m >>=$ a = join <$> liftM a <$> m

parseJSON (Object o) =
    SaveSection
        <$> o .:? "eventId"
        <*> o .:? "eventDate" >>=$ parseUSDate
        <*> o .:? "eventRecId"
        <*> o .:? "idxId"
        <*> o .:? "idxDate" >>=$ parseUSDate
        <*> o .:? "idxRecId"

(This should work...)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top