Adaskos07
Adaskos07

Reputation: 41

How to define a lambda function that filters list based on subtype of a sum type?

The example is taken from a "Haskell programming from first principles" The goal of filter function is get rid of all the objects except those of 'DbDate' type.

On somone's github I found a way to filter sum types with list comprehension and pattern matching(1). Now I am trying to find a way to redefine this filter with a lambda function(2) or normal "case of" of "if then" function. I do not know how to properly check the type of arguments of a function when I deal with custom data type.

Book doesn't introduce the reader to any super specific library functions, just standard maps, folds, filters and other stuff you'd find in prelude.

import Data.Time

data DatabaseItem = DbString String
                  | DbNumber Integer
                  | DbDate   UTCTime
                  deriving (Eq, Ord, Show)

--List that needs to be filtered
theDatabase :: [DatabaseItem]
theDatabase =
  [ DbDate (UTCTime (fromGregorian 1911 5 1)
                    (secondsToDiffTime 34123))
  , DbNumber 9001
  , DbString "Hello, world!"
  , DbDate (UTCTime (fromGregorian 1921 5 1)
                    (secondsToDiffTime 34123))
  ]



--1 works fine, found on someone's git hub
filterDbDate :: [DatabaseItem] -> [UTCTime]
filterDbDate dbes = [x | (DbDate x) <- dbes]

--2 Looking for the eqivalents with lambda or "case" or "if then"
--pattern is not satisfactory

filterDbDate :: [DatabaseItem] -> [UTCTime]
filterDbDate dbes = filter (\(DbDate x) -> True) theDatabase

Upvotes: 4

Views: 860

Answers (3)

bradrn
bradrn

Reputation: 8467

As @Niko has already said in his answer, filter cannot change the type. However, there is a variant of filter which can: Data.Maybe.mapMaybe :: (a -> Maybe b) -> [a] -> [b]. The idea is that if you want to keep an element, then you return Just newvalue from the lambda; otherwise you return Nothing. In that case, you could rewrite filterDbDate as:

import Data.Maybe

filterDbDate dbes = mapMaybe (\x -> case x of { DBDate d -> Just d; _ -> Nothing }) dbes

Personally, I would say that this is the second-clearest way to write this function (after the list comprehension method).

Upvotes: 4

Niko
Niko

Reputation: 401

filter has the type (a -> Bool) -> [a] -> [a] so it is not able to change the type of your list.

According to The Haskell 98 Report (section 3.11) the list comprehension used in the code you found on github desugars to:

filterDbDate2 :: [DatabaseItem] -> [UTCTime]
filterDbDate2 dbes = let extractTime (DbDate time) = [time]
                         extractTime _             = []
                     in concatMap extractTime theDatabase

You can rewrite extractTime to use case ... of:

filterDbDate3 :: [DatabaseItem] -> [UTCTime]
filterDbDate3 dbes = let extractTime item = case item of (DbDate time) -> [time]
                                                         _             -> []
                     in concatMap extractTime theDatabase

And replace it by a lambda:

filterDbDate4 :: [DatabaseItem] -> [UTCTime]
filterDbDate4 dbes = concatMap (\item -> 
    case item of 
        (DbDate time) -> [time]
        _             -> []) 
    theDatabase

But imho your original solution using list comprehension looks the best:

filterDbDate dbes = [x | (DbDate x) <- dbes]

Upvotes: 6

Lorenzo
Lorenzo

Reputation: 2210

You were indeed on the right track, as pattern matching is an easy way of solving this, however you will get error as your pattern-matching is not comprehensive. Also, note that if you use filter, you will still get a list of [DatabaseItem] as filter never changes the type. You can however use map to do it. So:

Case Of

You can have a case .. of inside your lambda function:

filterDbDate' :: [DatabaseItem] -> [UTCTime]
filterDbDate' = map (\(DbDate x) -> x) .filter (\x ->
  case x of
    DbDate x -> True
    _        -> False)

Recursion + Pattern Matching

However I think it's more clear using a recursion:

filterDbDate'' :: [DatabaseItem] -> [UTCTime]
filterDbDate'' [] = []
filterDbDate'' ((DbDate d):ds) = d : filterDbDate ds
filterDbDate'' (_:ds)          =     filterDbDate ds

Best Way

To be honest, when you have to mix up filter and map, and your lambdas are easy like this one, list comprehensions like yours are the cleanest way:

filterDbDate ds = [d | (DbDate d) <- ds]

Upvotes: 3

Related Questions