Learn You a Haskell for Great Good! - Miran Lipovaca [20]
| bmi <= fat = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
skinny = 18.5
normal = 25.0
fat = 30.0
Note
Notice that all the variable names are aligned in a single column. If you don’t align them like this, Haskell will get confused, and it won’t know that they’re all part of the same block.
where's Scope
The variables we define in the where section of a function are visible only to that function, so we don’t need to worry about them polluting the namespace of other functions. If we want to use a variable like this in several different functions, we must define it globally.
Also, where bindings are not shared across function bodies of different patterns. For instance, suppose we want to write a function that takes a name and greets the person nicely if it recognizes that name, but not so nicely if it doesn’t. We might define it like this:
greet :: String -> String greet "Juan" = niceGreeting ++ " Juan!"
greet "Fernando" = niceGreeting ++ " Fernando!"
greet name = badGreeting ++ " " ++ name
where niceGreeting = "Hello! So very nice to see you,"
badGreeting = "Oh! Pfft. It's you."
This function won’t work as written. Because where bindings aren’t shared across function bodies of different patterns, only the last function body sees the greetings defined by the where binding. To make this function work correctly, badGreeting and niceGreeting must be defined globally, like this:
badGreeting :: String
badGreeting = "Oh! Pfft. It's you."
niceGreeting :: String
niceGreeting = "Hello! So very nice to see you,"
greet :: String -> String
greet "Juan" = niceGreeting ++ " Juan!"
greet "Fernando" = niceGreeting ++ " Fernando!"
greet name = badGreeting ++ " " ++ name
Pattern Matching with where
You can also use where bindings to pattern match. We could have written the where section of our BMI function like this:
...
where bmi = weight / height ^ 2
(skinny, normal, fat) = (18.5, 25.0, 30.0)
As an example of this technique, let’s write a function that gets a first name and last name, and returns the initials:
initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
where (f:_) = firstname
(l:_) = lastname
We could have also done this pattern matching directly in the function’s parameters (it would have been shorter and more readable), but this example shows that it’s possible to do it in the where bindings as well.
Functions in where Blocks
Just as we’ve defined constants in where blocks, we can also define functions. Staying true to our healthy programming theme, let’s make a function that takes a list of weight/height pairs and returns a list of BMIs:
calcBmis :: [(Double, Double)] -> [Double]
calcBmis xs = [bmi w h | (w, h) <- xs]
where bmi weight height = weight / height ^ 2
And that’s all there is to it! The reason we needed to introduce bmi as a function in this example is that we can’t just calculate one BMI from the function’s parameters. We need to examine the list passed to the function, and there’s a different BMI for every pair in there.
let It Be
let expressions are very similar to where bindings. where allows you bind to variables at the end of a function, and those variables are visible to the entire function, including all its guards. let expressions, on the other hand, allow you to bind to variables anywhere and are expressions themselves. However, they’re very local, and they don’t span across guards. Just like any Haskell construct that’s used to bind values to names, let expressions can be used in pattern matching.
Now let’s see let in action. The following function returns a cylinder’s surface area, based on its height and radius:
cylinder :: Double -> Double -> Double
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^ 2
in sideArea + 2 * topArea
let expressions take the form of let