I'm playing around with a toy project in Haskell. I'm implementing some data structures I've built in other languages before to familiarize myself with how they'd be built in Haskell. This isn't my first functional language, I've built a couple of projects like a Scheme interpreter in OCaml but I think my OCaml experience is coloring how I'm approaching this problem. Its' not terribly important, but may be useful for context, to know that the data structure I'm implementing is a PR-Quadtree.
What I want to do is match and destructure a type inside a guard, a la OCaml's match statement.
data Waypoint = WayPoint {
lat :: Float,
lon :: Float,
radius :: Float,
speed :: Float,
accel :: Float
} deriving (Show)
data Region = Region {
x :: Float,
y :: Float,
width :: Float
} deriving (Show)
data PRQuadtree = WhiteNode Region
| BlackNode Region Waypoint
| GreyNode {
topLeft :: PRQuadtree,
topRight :: PRQuadtree,
botLeft :: PRQuadtree,
botRight :: PRQuadtree,
region :: Region
} deriving (Show)
getRegion node
| BlackNode(r, _) = r
| WhiteNode(r) = r
| GreyNode = region node
The getRegion function is the one I am having problems with in particular. In case what I'm trying to do is unclear: I'd like to simple extract one element of the argument but that depends on which member of the algebraic data type the argument is. In OCaml I could do:
let getRegion node = match node with
| BlackNode(r, _) = r
| WhiteNode(r) = r
| GreyNode = region(node)
(or something very similar, my OCaml is a bit rusty now).
In Haskell however, this doesn't appear to bind r in scope of the RHS of the guard branch. I've tried to look up Pattern Guards, as they sound similar to what I might want to do, but I can't really grok whats going on here. Really I just want to get a binding from the LHS of the = to the RHS of the equals (depending on which branch of the guard we've gone down).
Whats the idiomatic Haskell way to do what I'm trying to do here?
It can be achieved as follows:
getRegion :: PRQuadtree -> Region
getRegion (BlackNode r _) = r
getRegion (WhiteNode r) = r
getRegion GreyNode{region=r} = r
or even as
getRegion :: PRQuadtree -> Region
getRegion x = case x of
BlackNode r _ -> r
WhiteNode r -> r
GreyNode{} -> region x
In Haskell, prepending a type signature is very idiomatic.
Another option is extending the region field to the other cases as well:
data PRQuadtree = WhiteNode { region :: Region }
| BlackNode { region :: Region , waypoint :: Waypoint }
| GreyNode {
topLeft :: PRQuadtree,
topRight :: PRQuadtree,
botLeft :: PRQuadtree,
botRight :: PRQuadtree,
region :: Region
} deriving (Show)
Now, region will work on all PRQuadtree values.
Haskell uses | as ML does when defining algebraic datatypes, to separate different constructors, but does not use it to separate case branches, which instead follow the syntax
case .. of { pat1 -> e1 ; pat2 -> e2 ; ... }
which can be replaced by indentation
case .. of
pat1 -> e1
pat2 -> e2
...
Also, note that partial field selectors are discouraged:
data A = A1 { foo :: Int } | A2
Above, foo A2 type checks but crashes. On the other hand, when a field is present in all the constructors, we do not face such risk.
You can also write:
getRegion x
| BlackNode y <- x -> ....
| Greynode{} <- x -> ....
but it is quite unidiomatic in this simple case.
However, in more complex programs, this pattern matching in guards can be very useful. You use multiple equations or case to distinguish the general cases, like shown by #chi. But then, you can detect special cases, like in the following made up example:
getRegion x = case x of
BlackNode{region}
| [(0,_)] <- filter (inRegion region) interestingPoints
-> -- region encloses exactly 1 interesting point on x axis
....
| otherwise = ....
where
interestingPoints = .....
inRegion :: Region -> Point -> Bool
GreyNode{} -> ....
Related
for example
data CampingStuff = Apple String Int
| Banana String Int
| Pineapple String Int
| Table String Int
| Chairs String Int
I want to have a query function
pickStuff :: [CampingStuff] -> ??? -> [CampingStuff]
the ??? I want to pass Apple then the pickStuff is going to filter out all stuffs like
Apple "Jane" 3
Apple "Jack" 5
Apple "Lucy" 6
something I can think of is like
pickStuffs stuffs dummyStuff
= filter
(\x ->
(x == dummyStuff)
stuffs
pickStuffs stuffs (Apple "" 0)
instance Eq CampingStuff where
compare (Apple name1 number1) (Apple name2 number2)
= True
the drawback of it is :
passing extra parameters to dummy value is not elegant and is not making any sense "" 0
it has to implement all the value constructor in Eq type class ( Apple, Table , Chair)
it is not scalable as in the future I would like to filter out all the apples from Janes
like this (Apple "Jane" _)
Thank you for reading this and appreciate any help how to filter on this [CampingStuff] by Data Constructor like Apple/Table ?
The problem is that unsaturated constructors can't really be compared by value. The only things you can do with them are invoke them or pattern match on them. So if you want a function that tests for Apple, it'll have to be totally different from a function that tests for Banana - they can't share any code, because they have to compare against a different set of patterns.
This is all much easier if you refactor your type to remove the obvious duplication, leaving you with saturated value constructors. The generated Eq instance is all you'll need for comparing types:
data StuffType = Apple | Banana | Pineapple | Table | Chairs deriving Eq
data CampingStuff = Stuff { stuffType :: StuffType
, owner :: String
, quantity :: Int
}
Then you can easily write a function of type CampingStuff -> Bool by composing a couple functions.
hasType :: StuffType -> CampingStuff -> Bool
hasType t s = stuffType s == t
and use that to filter a list:
pickStuff :: StuffType -> [CampingStuff] -> [CampingStuff]
pickStuff = filter . hasType
In the comments, you ask: What if my constructors weren't all uniform, so I couldn't extract everything out to a product type with an enum in it?
I argue that, in such a case, you won't be happy with the result of a pickStuff function no matter how it's implemented. Let's imagine a simpler type:
data Color = Red | Green
data Light = Off | On Color
Now, you might wish to filter a [Light] such that it includes only lights that are On, regardless of their color. Fine, we can implement that. We won't even worry about generalizing, because the type is so small:
ons :: [Light] -> [Light]
ons = filter on
where on Off = False
on (On _) = True
Now you have lights :: [Light], and you can get onLights = ons lights :: [Light]. Amazing. What will you do with onLights next? Perhaps you want to count how many of each color there are:
import qualified Data.Map as M
colorCounts :: [Light] -> M.Map Color Int
colorCounts = M.fromListWith (+) . map getColor
where getColor (On c) = (c, 1)
colorCounts has a problem: it assumes all the lights are On, but there's no guarantee of that in the type system. So you can accidentally call colorCounts ls instead of colorCounts (ons ls), and it will compile, but give you an error at runtime.
Better would be to just do your pattern matching at the point when you'll know what to do with the results. Here, that's inside of colorCounts: just add a case for Off, and use mapMaybe instead of map so you have a chance to throw away values you don't like:
colorCounts' :: [Light] -> M.Map Color Int
colorCounts' = M.fromListWith (+) . mapMabye getColor
where getColor (On c) = Just (c, 1)
getColor Off = Nothing
The same arguments all hold for more complicated types: don't pattern match on a value until you're ready to handle all the information you might find.
Of course, one way to handle such information is to put it into a new type that contains only the information you want. So you could very well write a function
colorsOfOnLights :: [Light] -> [Color]
colorsOfOnLights = mapMaybe go
where go Off = Nothing
go (On c) = Just c
This way, you can't possibly mix up the input of the "filter" function with the output: the output is clearly divorced from the original Light type, and its values can only have come from the On lights. You can do the same thing for your CampingStuff type by extracting a new product type for each of the constructors:
data CampingStuff = Apple AppleData
| Banana BananaData
-- ...
data AppleData = AppleData String Int Bool
data BananaData = BananaData String Int
-- ...
asApple :: CampingStuff -> Maybe AppleData
asApple (Apple ad) = Just ad
asApple _ = Nothing
apples :: [CampingStuff] -> [AppleData]
apples = mapMaybe asApple
You'll need separate functions for asApple and for asBanana and so on. This seems cumbersome, and I don't exactly disagree, but in practice people don't really need large numbers of functions like this. It's usually better to do as I described before: delay the pattern match until you know what to do with the results.
For the function you want to have, you could create functions such as
isApple :: CampingStuff -> Bool
isApple Apple{} = True
isApple _ = False
and then use filter isApple. When you want to filter by Jane, you add another 5 functions for each type, like isAppleFrom :: String -> CampingStuff -> Bool and do filter (isAppleFrom "Jane").
Another approach is the following:
data StuffType = AppleT | BananaT | PineappleT | TableT | ChairsT deriving Eq
data Query = ByStuff StuffType | ByName String deriving Eq
pickStuff :: [CampingStuff] -> [Query] -> [CampingStuff]
pickStuff xs qs = filter cond xs
where
cond :: CampingStuff -> Bool
cond x = all (\q -> case (q, x) of
(ByStuff AppleT, Apple{}) -> True
...other pairs...
(ByName name1, Apple name2 _) -> name1 == name2
...
_ -> False) qs
That is, separate querying from the data types. The above is an example and may be written better.
I've created a new datatype Board:
data Board a = Grid [(Position, Maybe a)]
deriving (Eq, Show)
where Position is its own datatype:
data Position = NW | N | NE | W | M | E | SW | S | SE
deriving (Eq, Ord, Show)
Now I'm trying to create a function label, that takes a Position and Board and returns the label at the given position (wrapped using Just) or Nothing if the given position is empty.
I was thinking of implementing a new function Search to do this.
search :: (Eq a) => a -> [(a,b)] -> Maybe b
search _ [] = Nothing
search x ((a,b):xs) = if x == a then Just b else search x xs
But I don't know how to pass in the List [(a,b)] from my Board input. I tried:
label :: Position -> Board a -> Maybe a
label p b = Search p b
and got the error:
* Couldn't match expected type: [(Position, a)]
with actual type: Board a
* In the second argument of `lookup', namely `b'
In the expression: lookup p b
In an equation for `label': label p b = lookup p b
* Relevant bindings include
b :: Board a (bound at A6.hs:21:9)
label :: Position -> Board a -> Maybe a (bound at A6.hs:21:1)
Perhaps there's an easier way to go about this, but this is the only way I could think of.
(Aplet123 pointed out a mistake, since updated and updated the error produced)
You need to look into your data type under the wraps to find the actual datum there,
label :: Position -> Board a -> Maybe a
label p (Grid b) = search p b
-- ^^^^
Function names in Haskell must not be capitalized. That is reserved for types and data constructors.
The above will give you another type error but you'll be able to tweak it, I believe, to get it fixed. For starters, enter the definition without the type signature, to see what type is inferred for it.
Say I have something like this
class Circle c where
x :: c -> Float
y :: c -> Float
radius :: c -> Float
data Location = Location { locationX :: Float
, locationY :: Float
} deriving (Show, Eq)
data Blob = Location { blobX :: Float
, blobY :: Float
, blobRadius :: Float,
, blobRating :: Int
} deriving (Show, Eq)
instance Circle Location where
x = locationX
y = locationY
radius = pure 0
instance Circle Blob where
x = blobX
y = blobY
radius = blobRadius
Say for example I want Circle types to be equal if their x and y points are equal. How can I compare instances of the type class with the (==) and (/=) operators. I know I can do something like this, but is it possible to overload the operators?
equal :: Circle a => Circle b => a -> b -> Bool
equal a b = (x a == x b && y a == y b)
I want to be able to compare with
(Location 5.0 5.0) == (Blob 5.0 5.0 ... ) should give me True
Zeroth, some standard imports:
import Data.Function (on)
import Control.Arrow ((&&&))
First, this is not a good idea. a==b should only be true if a and b are (for all purposes relevant to the user) interchangeable – that's clearly not the case for two circles which merely happen to share the same center point!
Second, it's probably not a good idea to make Circle a typeclass in the first place. A typeclass only makes sense when you want to abstract over something that can't directly be expressed with just a parameter. But if you just want to attach different “payloads” to points in space, a more sensible approach might be to define something like
data Located a = Located {x,y :: ℝ, payload :: a}
If, as seems to be the case, you actually want to allow different instances of Circle to coexist and be comparable at runtime, then a typeclass is entirely the wrong choice. That would be an OO class. Haskell doesn't have any built-in notion of those, but you could just use
data Blob = Blob
{ x,y :: ℝ
, radius :: ℝ
, rating :: Maybe Int }
and no other types.
https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/
Third, the instance that you asked for can, theoretically speaking, be defined as
instance (Circle a) => Eq a where
(==) = (==)`on`(x &&& y)
But this would be a truely horrible idea. It would be a catch-all instance: whenever you compare anything, the compiler would check “is it of the form a?” (literally anything is of that form) “oh great, then said instance tells me how to compare this.” Only later would it look at the Circle requirement.
The correct solution is to not define any such Eq instance at all. Your types already have Eq instances individually, that should generally be the right thing to use – no need to express it through the Circle class at all, just give any function which needs to do such comparisons the constraint (Circle a, Eq a) => ....
Of course, these instances would then not just compare the location but the entire data, which, as I said, is a good thing. If you actually want to compare only part of the structure, well, make that explicit! Use not == itself, but extract the relevant parts and compare those. A useful helper for this could be
location :: Circle a => a -> Location
location c = Location (x c) (y c)
...then you can, for any Circle type, simply write (==)`on`location instead of (==), to disregard any other information except the location. Or write out (==)`on`(x &&& y) directly, which can easily be tweaked to other situations.
Two circles that share a common center aren't necessarily equal, but they are concentric; that's what you should write a function to check.
concentric :: (Circle a, Circle b) => a -> b -> Bool
concentric c1 c2 = x c1 == x c2 && y c1 == y c2
i have a few questions
Im writing this constructor called rope which i have like this
data Rope = TextRope{ropeText :: String}
| ConcatRope{rope1 :: Rope, rope2 :: Rope}
| SubRope{subRopetext :: Rope, starting :: Integer, ending :: Integer}
deriving Show
First off when i make a TextRope like this
*Main> let s =TextRope "why"
*Main> s
TextRope {ropeText = "why"}
*Main>
when i do s i want to just get the string of the constructor which is why and im not really sure about that.
Also curious about concat and sub constructors. Specifically it seems like to me you are calling these two constructors there is things happening, you are returning the result of concatenating rope 1 and rope 2 together, im not sure how to describe that in this language, you are defining the data structure but somehow the return of that is a result that has to be calculated by the structure
Here are some examples of what how these functions work
> let tr = TextRope "Hello,"
> let sr = TextRope " world!"
> let hw = ConcatRope tr sr
> let ow = SubRope hw 4 4
> tr
Hello,
> sr
world!
> hw
Hello, world!
Sort of confused overall, new to haskell constructors and datatypes, so some pointers would be helpful (not c pointers though!)
Data constructors never do work. They only hold the data you pass into them. If you want work to be done, you should define what are called smart constructors, which are just functions that perform some operation before passing it to the actual constructor. An example might be
data Fraction = Fraction
{ numerator :: Int
, denominator :: Int
} deriving (Eq)
(%) :: Int -> Int -> Fraction
x % y =
let (a, b) = reduce x y
in Fraction a b
-- Reduces a fraction to it's simplest terms
reduce :: Int -> Int -> (Int, Int)
reduce num den = undefined
Here you wouldn't export the Fraction constructor from your module, just the % function that constructs one in the most reduced form.
The other problem you have is that you want your constructors to print out differently. You can achieve this by not deriving Show. However, I will warn that the Haskell convention is that show . read = read . show = id, which wouldn't hold for what you want to do. This isn't a strict convention, though, and there's nothing stopping you from doing something like:
data Rope = <your implementation minus the deriving Show bit>
instance Show Rope where
show (TextRope t) = t
show (ConcatRope r1 r2) = show r1 ++ show r2
show (SubRope r starting ending) = <exercise left to reader>
As an aside, I would recommend against having a sum type of records with different field names, this can lead to problems where your program type-checks but contains errors that can be caught at compile time if written differently. For example, what would happen if you had the code
> ropeText (ConcatRope (TextRope "Hello, ") (TextRope "world!"))
This would cause an error and crash your program! Instead, it looks like you just want a Rope type with concat and subRope functions, so you could implement it very simply as
data Rope = Rope String deriving (Eq)
concatRope :: Rope -> Rope -> Rope
concatRope (Rope r1) (Rope r2) = Rope (r1 ++ r2)
-- Why use Integer instead of Int? You might find it's easier to implement this function
subRope :: Rope -> Integer -> Integer -> Rope
subRope (Rope r) start end = Rope $ substr start end r
where substr s e text = <exercise left to reader>
Now there's absolutely no way to have an illegal rope operation, the only difference is now you have to use concatRope in place of ConcatRope and subRope in place of SubRope. You're guaranteed that these functions will do what you want, you don't have some complicated type that doesn't help you anyway.
if you don't implement your own show (not with auto-deriving) you will have a harder time getting what you want.
But if you do it's kindof easy:
data Rope = TextRope{ropeText :: String}
| ConcatRope{rope1 :: Rope, rope2 :: Rope}
| SubRope{subRopetext :: Rope, starting :: Integer, ending :: Integer}
instance Show Rope where
show (TextRope s) = s
show (ConcatRope a b) = show a ++ show b
I'm sure you'll find the implementation for the SubRope case youself ;)
Your example code and your example interactive results don't match. This is how you've defined Rope:
data Rope = TextRope{ropeText :: String}
| ConcatRope{rope1 :: Rope, rope2 :: Rope}
| SubRope{subRopetext :: Rope, starting :: Integer, ending :: Integer}
deriving Show
The deriving Show part is key there; we'll see how.
Later you show this example output:
> let tr = TextRope "Hello,"
> let sr = TextRope " world!"
> let hw = ConcatRope tr sr
> hw
Hello, world!
With the code that I just showed, actually, what you'll see is the following:
> hw
ConcatRope { rope1 = TextRope "Hello,", rope2 = TextRope " world!" }
The only way we could get the output that you describe is if we got rid of the deriving Show clause in your definition of Rope, and wrote this:
instance Show Rope where
show (TextRope text) = text
show (ConcatRope r1 r2) = show r1 ++ show r2
show (SubRope rope start end) = ...
I'm trying to write a simple program in Haskell that can determine someone's body mass index.
Here's what I have written:
type Height = Float
type Weight = Float
type PeopleStats = [(String, Height, Weight)]
and...
bmi :: Height -> Weight -> Float
bmi heightCm weightKg = weightKg/(heightCm)^2
healthy :: Height -> Weight -> Bool
healthy heightCm weightKg | 25 > index && 18 < index = True
| otherwise = False
where index = bmi heightCm weightKg
So far, the function "healthy" can calculate someone's BMI, and the function "healthyPeople" returns a boolean statement determining if the person's BMI falls within the limits which is considered normal for a healthy person.
I want to write a function called "healthyPeople".
healthyPeople :: PeopleStats -> [String]
This function needs to take a list of PeopleStats and returns a list of names (Strings) of people who are deemed to be "healthy" from the "healthy" function.
For example:
If I input [("Lee", 65, 185), ("Wang", 170, 100), ("Tsu", 160, 120)] I will get a list of the names of the people whose BMI returns true form the boolean function in "healthy".
Please help !!!!
First, I think you probably meant to define bmi as:
bmi :: Height -> Weight -> Float
bmi heightCm weightKg = weightKg/(heightCm/100)^2
since the formula uses height in meters.
Now, here's a step by step way to do it using helper functions. I defined a type:
type PersonStats = (String, Height, Weight)
and some functions on that type:
healthyPerson :: PersonStats -> Bool
healthyPerson (name, h, w) = healthy h w
getName :: PersonStats -> String
getName (name, h, w) = name
With those in place, the final function becomes trivial:
healthyPeople :: PeopleStats -> [String]
healthyPeople people = map getName $ filter healthyPerson people
or in point-free notation:
healthyPeople :: PeopleStats -> [String]
healthyPeople = map getName . filter healthyPerson
First you filter out the healthy people from the list, then you map the list of stats into a list of names.
You can express the entire function in one go without the helpers if you use lambdas.
There's a standard Haskell function named filter that does exactly (well, almost) what you want here. It has type (a -> Bool) -> [a] -> [a], i.e., it takes a predicate and a list and returns the members that satisfy the predicate.
You can't apply it directly to PeopleStats because the types don't match up, but it's not hard to write a function to connect the two:
healthyPerson :: (String, Height, Weight) -> Bool
healthyPerson (_, h, w) = healthy h w
healthyPeople :: [(String, Height, Weight)] -> [String]
healthyPeople people = map name $ filter healthyPerson people
where name (s, _, _) = s
This does what you want.
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 :))
First off, note that your definition of BMI is incorrect - you need to convert centimetres into metres:
bmi heightCm weightKg = weightKg/(heightCm/100)^2
With that fixed, I came up with the following:
healthyPeople :: PeopleStats -> [String]
healthyPeople [] = []
healthyPeople ((name, height, weight):xs) | healthy height weight = name : healthyPeople xs
| otherwise = healthyPeople xs
This is fairly straight forward. It uses list-based recursion to recurse over all the elements of the list, and it uses guards similarly to how you used them in the healthy function to switch behaviour based on whether the person at the head of the list is healty or not. If they are healthy, their name is concatted with the result of processing the rest of the list.
Next time, you should try solving the problem yourself and then ask for help (and show what you've tried). You'll learn far more!
type Height = Float
type Weight = Float
data PersonStats = PersonStats
{ personName :: String, personHeight :: Height, personWeight :: Weight }
bmi :: Height -> Weight -> Float
bmi heightCm weightKg = weightKg/(heightCm / 100)^2
healthy :: Height -> Weight -> Bool
healthy heightCm weightKg = 25 > index && 18 < index
where index = bmi heightCm weightKg
healthyPerson :: PersonStats -> Bool
healthyPerson p = healthy (personHeight p) (personWeight p)
healthyPeople :: [PersonStats] -> [String]
healthyPeople = map personName . filter healthyPerson