2

Going over the "Learn yourself..." book I encountered a BMI calculator app. The app, so far, takes a list of pairs of Double and returns a list of Double:

calcBMI :: [(Double, Double)] -> [Dobule]
calcBMI xs = [bmi w h | (w, h) <- xs]
    where bmi weight height = weight / height ^ 2

Trying to apply another function to each element of the returned list I tried map declareBMI head calcBMI [(w, h) | w <- [60..70], h <- [1.65]] but got an error (where declareBMI was defined):

declareBMI :: Double -> String
declareBMI bmi
    | bmi <= 18.5 = "Skinny"
    | bmi <= 25 = "Norm"
    | bmi <=30 = "Overweight"
    | otherwise = "OBESE!"

After viewing the definition of map (i.e. map f x:xs = f x : map f xs) I figured out the problem: I'm trying to call head on the remainder of a lazy list, meaning the interpreter tries to first run calcBMI and only then head-ing the result... and this will of course fail because calcBMI requires a list of pairs.

So, my question is

  1. is there a way to force an eager evaluation of the calcBMI function so I can run head on the resultant list?
  2. Is there a better way then forcing eager evaluation to allow me to head each element of the resultant list?
duplode
  • 33,731
  • 7
  • 79
  • 150
  • 6
    `map head calcBMI [(w, h) | w <- [60..70], h <- [1.65]]` is just ill-typed, so laziness isn't particularly relevant here. Also, the list resulting from `calcBMI` has type `[Double]`, so it's not possible to `head` each element, because `head` works on lists and `Double`-s aren't lists. Could you clarify what functionality you'd like to achieve? – András Kovács Jul 26 '15 at 09:20
  • Hi. The result I'd like to get is that I run another function, say `declareBMI` that takes the numeric value `calcBMI` computed and outputs a string saying "You're OK", "You're fat", etc. To the best of my understanding `calcBMI` as I defined it returns a list of Double: `[Double]`. I can see why `map head` won't work... now that I think about it, but I also tried: `map declareBMI head calcBMI...` and it failed too. –  Jul 26 '15 at 09:42
  • Rethinking my code I see why `map` is **not** the right way to go, starting from the second element there's nothing to `head`, it is ill-typed as Andras Kovacs states! So, I'm now at a complete loss as to how to generate the list of declarations from the list of doubles. –  Jul 26 '15 at 09:50

1 Answers1

0

Make sure the types line up. If you're unsure about types or get compiler errors, try to break up your code into smaller expressions and use GHCi or TypedHoles to inspect types.

An example GHCi session:

> let xs = [(w, h) | w <- [60..70], h <- [1.65]]
> :t calcBMI xs
calcBMI xs :: [Double]

Here we asked the type of calcBMI xs, and got [Double] as expected.

Now we can take the first element of calcBMI xs using head:

> head (calcBMI xs)
22.03856749311295

We can use head because its input is a list, and calcBMI xs is a list.

> :t head
head :: [a] -> a

declareBMI has type Double -> String, so we can use it on plain Double values, but we can also use it on values inside lists with map.

> let bmis = calcBMI xs
> declareBMI (head bmis)
"Norm"

Here we used declareBMI on a Double. We could also first use declareBMI on on the list elements, and apply head afterwards.

> :t map declareBMI bmis
map declareBMI bmis :: [String]
> map declareBMI bmis
["Norm","Norm","Norm","Norm","Norm","Norm","Norm","Norm","Norm","Overweight","Overweight"]
> head (map declareBMI bmis)
"Norm"

Also, don't forget that in Haskell we apply functions just by whitespace. So head (map declareBMI bmis) is correct, because map takes two arguments here, and head takes one. We don't parenthesize function calls, but rather individual arguments to a function.

András Kovács
  • 29,931
  • 3
  • 53
  • 99
  • Excellent! Thank you very much. Thanks for walking me through it, not just putting some code and letting me figure it out. Also, comment about parenthesis -- taken to heart. –  Jul 26 '15 at 10:34