Using Filter that requires multiple Input - Haskell

Viewed 95

I'm relatively new to Haskell, and have been trying to learn it for the past few weeks, but have been stuck on Filters and Predicates, which I was hoping to get help with understanding.

I have come across a problem whereby I have a list of tuples. Each tuple consists of a (songName, songArtist, saleQty) and am required to remove a tuple from that list based on the user inputting the songName and SongArtist.

When returning the results, I understand I can remove a tuple by using the Filter function when returning the results. I have been doing some reading on it using LYAH. Its taught me that I have to use a Predicate (which is another function) to filter through my results. This has caught me off guard as I learnt a Filter function has type (a -> Bool) -> [a] -> [a], which means my input for the Filter needs to to be a Boolean and my output for my Predicate needs to be Boolean so it can be fed to the Filter.

This is a problem, as to filter my results from the list I need to input the songName and songArtist (both of which are String types) to the predicate when recursively going through the results, and output the songName and songArtist to the Filter to let it know which exact tuple needs to be removed from the list.

Am I going about this the wrong way or is there a better way I could go about this?

2 Answers

I learnt a Filter function has type (a -> Bool) -> [a] -> [a]

The filter :: (a -> Bool) -> [a] -> [a] takes two parameters, a predicate with signature a -> Bool, and a list of items, and it returns a list of items that satisfy the predicate. So the predicate is the first parameter.

which means my input for the Filter needs to to be a Boolean.

No the first parameter has type a -> Bool, so it is a function that maps an item a to a Bool, so a predicate.

You can for example create a function that checks if both the songName and songTitle match with:

filterSales :: String -> String -> [(String, String, Int)] -> [(String, String, Int)]
filterSales artist title items = filter p items
    where p (artist', title', _) = artist == artist' && title == title'

Here p is thus the predicate, a function that maps a 3-tuple to a boolean. The predicate p will return True for a 3-tuple if the first two items are equal to the artist and title respectively.

This is a second answer and the existing one is great, so I'll show my alternate take; after all, two explanations can't be worse than one, right?

What filter expects of you is a function that will tell it one thing - should I keep the given element in the resulting collection?

The type of that function is (a -> Bool), and that's what we call a predicate.

In your specific case, assuming

type SongEntry = (SongName, SongArtist, SaleQty)

It's gonna be a function of type SongEntry -> Bool. Now, there could be a lot of such functions... maybe you want more than 100 sales?

hasMoreThan100Sales :: SongEntry -> Bool
hasMoreThan100Sales (_, _, sales) = sales > 100

To use it:

filter hasMoreThan100Sales songs

That should be easy enough. What if we wanted more than n sales? This is where Haskell's curry-by-default really shines. We can add one additional parameter:

hasMoreThanNSales :: Int -> SongEntry -> Bool

Which we can also read and understand as Int -> (SongEntry -> Bool). The implementation is straighforward at this point:

hasMoreThanNSales n (_, _, sales) = sales > n

And crucially, to get our previous "100" function, we just need to apply it:

hasMoreThanNSales 100 :: SongEntry -> Bool

This has the type we need to use it with the filter:

filter (hasMoreThanNSales 100) songs

At this point, you should understand it well enough to write your own predicate that can be parametrized in any way you want.

Oh, and one more thing that might be confusing. hasMoreThan100Sales is a predicate. hasMoreThanNSales is not, until you apply it with a value (e.g. 100) - (hasMoreThanNSales 100) is a predicate.

Related