Search code examples
haskellgeneric-programmingfoldscrap-your-boilerplate

SYB: can a map over the result of listify be rewritten with a gfoldl?


Can I use SYB's gfoldl to do the map over the result of listify in one go?

Consider for example the following code:

extractNums :: Expr -> [Int]
extractNums e = map numVal $ listify isNum e

  where isNum :: Expr -> Bool
        isNum (Num _) = True
        isNum _       = False

        numVal :: Expr -> Int
        numVal (Num i) = i
        numVal _       = error "Somehow filter did not work?"

I do not like that in the numVal function I have to consider the different data constructors of the Expr type while I am only interested in the Num constructor. I rather would replace isNum and numVals with something like the vals function below:

    vals :: [Int] -> Expr -> [Int]
    vals xs (Num x) = x : xs
    vals xs _       = xs

Can this be done with gfoldl? How?


Solution

  • Function listify is defined as

    -- | Get a list of all entities that meet a predicate
    listify :: Typeable r => (r -> Bool) -> GenericQ [r]
    listify p = everything (++) ([] `mkQ` (\x -> if p x then [x] else []))
    

    which is similar to filter. We could create an alternative that resembles mapMaybe which combines map and filter you need into one:

    import Data.Generics
    import Data.Generics.Schemes
    import Data.Maybe (maybeToList)
    import Data.Typeable
    
    listify' :: (Typeable t) => (t -> Maybe r) -> GenericQ [r]
    listify' f = everything (++) ([] `mkQ` (maybeToList . f))
    

    Then your example could be expressed as

    numVal :: Expr -> Maybe Int
    numVal (Num i) = Just i
    numVal _       = Nothing
    
    test :: Expr -> [Int]
    test = listify' numVal