0

I've been doing some Haskell exercises from a Haskell book, and one of the tasks is for me to filter for values of a certain type and return them as a list.

import Data.Time

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


database :: [Item]
database =
  [
    DbDate (UTCTime (fromGregorian 1911 5 1) (secondsToDiffTime 34123)),
    DbNumber 9001,
    DbString "Hello World!",
    DbDate (UTCTime (fromGregorian 1921 5 1) (secondsToDiffTime 34123))
  ]

That's the code I am given to work with, and for my first task:

Write a function that filters for DbDate values and returns a list of the UTCTime values inside them. The template for the function is:

filterDate :: [Item] -> [UTCTime]
filterDate = undefined

What I have to use here are folds since that is the matter concerned here.

I looked up the Data.Time module on Hoogle and that didn't really help since I couldn't understand how to interact with the module. Maybe I'm looking at this from a wrong perspective because I don't think it has something to do with the filter function, and I don't think it has something to do with type-casting neither ::.

How do I get UTCTime values, and how do I filter for them?

Poriferous
  • 1,566
  • 4
  • 20
  • 33
  • I said "What makes you think you should be using a fold? The question doesn't imply that: I read it as saying you need to filter the list of `Item` to return `DbDate` values and then unpack the `UTCTime` from each value." ... but I think I'm seeing the light now. You can implement a filter as a fold by walking across your import list and concatenating valid values onto an output list. – simpleigh Jun 13 '16 at 21:54
  • `::` isn't type casting. – melpomene Jun 13 '16 at 21:55
  • Has the book introduced list comprehensions yet? – melpomene Jun 13 '16 at 21:55
  • @melpomene The book has introduced me to list comprehensions aye. Should I be using them to decimate the database for UTCTime values? – Poriferous Jun 13 '16 at 21:56
  • 1
    Yeah, a list comprehension is probably the most straightforward solution to this problem. BTW, you don't need to interact with `Data.Time` for this task. – melpomene Jun 13 '16 at 21:57
  • @melpomene So, how do I go about using list comprehensions to complete this task? I'd imagine something like `[x | x <- database, ...]`? – Poriferous Jun 13 '16 at 22:01
  • @simpleigh Yeh, that's what the task might be asking me to do. There are other ways as @melpomene has described. But I think the task wants me to use folds, since the tasks are in a chapter regarding folds and their uses. It did demonstrate that I could assert types with `::` so it might have something to do with that. – Poriferous Jun 13 '16 at 22:06
  • @Poriferous For the record, the comprehension-based definition would be `filterDate items = [time | DbDate time <- items]`. This exploits that in list comprehensions, pattern failures (i.e. non-`DbDate` items) are silently skipped. – Frerich Raabe Jun 14 '16 at 09:22
  • @FrerichRaabe That's very interesting! I didn't know that. But then, how can we create a generic filter function that can get values by type? For example, would it be possible to pass a type to the function so that it can get values of that type? I do notice that these getters/filters have similar behaviour/functionality. So, something like `filterBy TYPE items = [t | TYPE t <- items]` could be valid? Then I could call the function as `let m = filterBy DbDate database` which would return all the values of type `DbDate` from the `database`. – Poriferous Jun 14 '16 at 12:04
  • @Poriferous I don't think that generalization gets you very far, but you could define a function `Item -> Maybe UTCTime` via `f x = case x of DbDate t -> Just t; _ -> Nothing`. And then use `mapMaybes f` to get a function `[Item] -> [UTCTime]`. `mapMaybes` is from `Data.Maybe`. – Frerich Raabe Jun 14 '16 at 14:16

2 Answers2

5

OK, my Haskell-fu is extremely weak but I'm going to have a stab at an answer. You're looking to define a function that walks across a list and filters it. If the value is a DbDate then you return <that value> : <output list>, otherwise you return <output list>. By folding over the input you produce a filtered output. There's a relevant question at How would you define map and filter using foldr in Haskell? which might explain this better.

This breaks down to something like:

filterFn :: Item -> [UTCTime] -> [UTCTime]
filterFn (DbDate x) xs = x:xs
filterFn _ xs = xs

(this might be a syntax fail). This function takes an item off our [Item] and pattern matches.

  • If it matches DbDate x then x is a UTCTime and we append it to our input list.
  • If it doesn't then we ignore it and return the input list unchanged.

We can then fold:

filterDate = foldr filterFn []

Does that get you to an answer?

Community
  • 1
  • 1
simpleigh
  • 2,854
  • 18
  • 19
  • Not quite because DbDate requires an argument before it can be used for pattern matching. The compiler complains about the lack of arguments to DbDate. – Poriferous Jun 13 '16 at 22:28
  • Try `filterFn (DbDate x) xs = x:xs` and `filterFn _ xs = xs`. Only two arguments must be used here, so parentheses are needed. – chi Jun 13 '16 at 22:35
  • @chi I think that's more along the lines of what the compiler agrees with. I modified the filter function then I used `foldr filterDate [] database` which returns the time values as a list. It was more an issue as to how I 'fetch' the right types. My only concern here is that this function actually doesn't have the same type signature. From what I have, filterDate's type signature is `Item -> [UTCTime] -> [UTCTime]`. – Poriferous Jun 13 '16 at 22:45
  • @Poriferous are you sure? Using your definition of `Item` and my definitions of `filterFn` and `filterDate` above, `ghci` gives me the following type signature: `filterDate :: [Item] -> [UTCTime]` which is what we want. – simpleigh Jun 13 '16 at 23:06
  • Being a C++ programmer, I naturally renamed the `filterFn` functions to getters which helps me understand things a bit better. Thanks to you two I've been able to solve all tasks seamlessly. :-) – Poriferous Jun 13 '16 at 23:36
0

Item is defined as a union type, which means it can be a DbString, a DbNumber or a DbDate.

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

You can use pattern matching to get only the value you're interested in. You need to match on an item, check whether it is a DbDate and if that's the case extract the UTCTime instance it holds.

You said you want to use a fold so you need an accumulator where you can put the values you want to keep and a function to populate it.

filterDate items = foldl accumulate [] items
    where extractTime item = case item of DbDate time -> [time]
                                          _ -> []
          accumulate item accumulator = accumulator ++ (extractTime item)

In the code above you have extractTime that pattern matches over an item and either returns a list containing the time or it returns an empty list. The accumulate function just puts together the values you got from the previous steps (they're stored in accumulator) and the value you got applying extractTime to the current item.

mariosangiorgio
  • 5,520
  • 4
  • 32
  • 46