Search code examples
haskellhaskell-lens

How to remove speicific value constructor in a list using traversal


data Fruit = Apple Int
           | Banana Color Int
           
data Color = Red
           | Green
           
let basket = [Apple 3,Banana Red 30,Apple 6]

The goal is to keep Banana only

How to remove all Apple {} in the list basket ? using traversal (https://hackage.haskell.org/package/lens-5.3.2/docs/Control-Lens-Traversal.html) ?

Once the traversal is built then I can remove them , modify them (over) , change them (set)

Thank you !


Solution

  • It can't be done using Traversals. Neither traverse from base nor the traversals from lens allow for that.

    The reason is that traversals have the important property that they don't change the overall "shape" of the container. They can't add or remove elements. As the docs for Traversable say:

    Functors representing data structures that can be transformed to structures of the same shape

    It's true that the lens documentation says that traversals let you "focus" on certain elements of a structure. But what is meant is that you can set those elements, or modify them. You can't delete them.


    If you are looking for a Traversable-like class for containers that allow (potentially effectful) filtering operations, there's Filterable and Witherable from the witherable package.


    But in your case, I would simply do something like

    removeApples :: [Fruit] -> [Fruit]
    removeApples = mapMaybe (\f -> case f of
       Apple {} -> Nothing
       other -> Just other)
    
    removeApples2 :: [Fruit] -> [Fruit]
    removeApples2 = foldMap (\f -> case f of
       Apple {} -> []
       other -> [other])
    
    removeApples3 :: [Fruit] -> [Fruit]
    removeApples3 fruits = do -- list monad
       fruit <- fruits
       case fruit of 
        Apple {} -> []
        other -> [other]
    
    removeApples4 :: [Fruit] -> [Fruit]
    removeApples4 = filter (\f -> case f of
       Apple {} -> False
       other -> True)