Reputation: 41
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
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
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
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:
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)
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
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