Let's think about what you want to do. You have a list, and you want to (a) select only certain items from the list, and (b) do something to each element of the list. This being Haskell, let's express this in types. The first thing you need—well, it'll have to take a list [a]
, and a way to check if each element is good. How can it check? Well, it should be a function a -> Bool
. And it should give us back a smaller list. In other words, something like [a] -> (a -> Bool) -> [a]
. Then we want to take our list and do something to each element. In other words, we'll need a list [a]
, and a function a -> b
. Thus, we'll want something of the type [a] -> (a -> b) -> [b]
. Now that we have the types, we're golden: we can use Hoogle to search for them. I highly, highly recommend using Hoogle regularly; it's a Haskell search engine which searches both types—the uniquely awesome part—and function/datatype/typeclass/module/package names. The first function, as it turns out, is the second result for the query: filter :: (a -> Bool) -> [a] -> [a]
. This takes a function and a list and returns only the elements of the list for which the function is true. The second function is the first result, map :: (a -> b) -> [a] -> [b]
, which calls the given function on every element of the given list and returns a list of the results. Note that the arguments have the function, not the list, first; this is more natural, as you'll see shortly.
We want to put these two together for healthyPeople
:
healthyPeople :: PeopleStats -> [String]
healthyPeople sts = map (\(n,_,_) -> n) $ filter (\(_,h,w) -> healthy h w) sts
This does what you want. $
is function application, but effectively groups the right-hand side because of its precedence; this allows us to elide parentheses. Here we see why it's nice to have map
take its function first; we pass it the name-extracting function ((n,_,_)
is a pattern which will match a triple and assign n
its first element, ignoring the other two), and then (via $
) the filtered list.
This is nice, but not how I'd actually write it. Since sts
is the last parameter to the function and to its body, it's unnecessary. In truth, all functions in Haskell take only one argument; this means that if you don't pass enough arguments, you get a function which expects the missing arguments and returns the result. With the help of the function-composition operator .
, this gives us
healthyPeople :: PeopleStats -> [String]
healthyPeople = map (\(n,_,_) -> n) . filter (\(_,h,w) -> healthy h w)
And that's probably how I'd write it! You'll find yourself using map
and filter
a lot; they're real workhorses in functional programming.
There is another idiomatic way you can write healthyPeople
; you can use a list comprehension, as follows:
healthyPeople :: PeopleStats -> [String]
healthyPeople stats = [n | (n,h,w) <- stats, healthy h w]
This reads as "construct the list of every n
such that (n,h,w)
is an element of stats
and healthy h w
is true. If any of the pattern matches or the predicates fail (you can have more than one of each, though you don't need that here), that element is skipped; otherwise, the left side of the |
is executed. It's effectively another way of writing the map
/filter
version.
Edit 1: As many others are saying, your units are off in bmi
; you should have heightCm/100
. Also, your healthy
function has code equivalent to
f x | cond = True
| otherwise = False
This is equivalent to writing, in a C-like,
bool f(some_type x) {
if (cond)
return true;
else
return false;
}
Instead, you should just write
bool f(some_type x) {
return cond;
}
Or, in this case
f x = cond
This gives you the shorter code
healthy :: Height -> Weight -> Bool
healthy heightCm weightKg = let index = bmi heightCm weightKg
in 25 > index && 18 < index
(You can use a where
clause too, but here's a let
just because I like it better :))