Search code examples
listhaskellrecords

Recursively search for a record attribute and collect into list in Haskell


Hi I'm trying to get how I would collect the same attibute of a (possibly) deeply nested data structure. The following data types are

data PageContent = 
PageElement { content_type :: String
            , element :: String
            , attributes :: Maybe Object
            , services :: Maybe [ServiceRequest]
            , body :: Maybe [PageContent]
            , styles :: Maybe Object
            , scripts :: Maybe Object }
| PageComponent { content_type :: String
                , id :: String
                , tag :: Maybe String
                , args :: Maybe Object
                , services ::Maybe [ServiceRequest]}
| PageConditional { content_type :: String
                  , true :: Maybe [PageContent]
                  , false :: Maybe [PageContent]
                  , condition :: String }
deriving(Show)

So in my case I'm trying to collect all ServiceRequest that are declared. Elements are passed in as a list of [PageContent]. I think it would be perfect to use a recursive function because for example when I hit a PageElement that element could have a _body which has a Maybe [PageContent], so going through the _body of a PageElement should be identical to going through the original [PageContent] list passed in, given that I extract it from Maybe.

I have a hard time figuring out how I would return this as a new list

getPageContentServiceRequests :: [PageContent] -> Maybe [ServiceRequest]

So I could have no ServiceRequest or I could have a list of 1 or more ServiceRequests. If I do this recursively, would I pass two lists to the function? One with [PageContent] and then an empty list which I could add to recursively while collecting ServiceRequests?

example

getPageContentServiceRequests (x:xs) (y:ys) =
    case (isJust (body x)) of
        True -> y : getPageContentServiceRequests fromJust (body x) ys

Would this be the correct approach? I feel I understand the recursive concept when I'm working with a list of the same type but not if I have to construct a completely new list with a different type.


Solution

  • Maybe a is a Monoid as long as a is a Monoid:

    instance Monoid a => Monoid (Maybe a)
    

    Therefore, we can map each PageContent to a Maybe [ServiceRequest] and mconcat them.

    The PageElement has a services :: Maybe [ServiceRequest], but it may also have more Maybe [ServiceRequest]s under the body :: Maybe [PageContent]. We can use getPageContentServiceRequests on the body, using >>= to join the two levels of Maybe that would be introduced, and use <> (aka mappend) to append them with services. We can do the same for PageComponent and PageConditional:

    import Data.Monoid
    
    getPageContentServiceRequests :: [PageContent] -> Maybe [ServiceRequest]
    getPageContentServiceRequests = mconcat . map getServiceRequests where
    
      getServiceRequests :: PageContent -> Maybe [ServiceRequest]
      getServiceRequests PageElement { services=services, body=body } =
        services <> (body >>= getPageContentServiceRequests)
      getServiceRequests PageComponent { services=services } =
        services
      getServiceRequests PageConditional { true=true, false=false } =
        (true >>= getPageContentServiceRequests) <>
        (false >>= getPageContentServiceRequests)