Where bindings in Haskell

In the previous section, we defined a BMI calculator function in which we repeated the expression weight/height ↑ 2 three times. It would be better if we could calculate it once, bind it to a name and then use that name instead of the expression. We can modify our function like this:

ghci 77> let {bmiTell :: (Floating a, Ord a) ⇒ a → a → String;
			bmiTell weight height
			| bmi 6 18.5 = "underweight range"
			| bmi 6 25.0 = "normal range"
			| bmi 6 30.0 = "overweight range"
			| otherwise = "obese range"
			where bmi = weight / height ↑ 2}
ghci 78> bmiTell 85 1.90
"normal range"

We put the keyword where after the guards (when you’re not using ghci, indent it as much as the pipes are indented), and then we define names or functions. These names / functions are visible across the guards.

Now we don’t have to repeat ourselves, which improves readability. Moreover, if we decide that we want to calculate BMIs a bit differently (e.g., using pounds and inches), we only have to change it once. Finally, this can make our program faster since our BMI function is calculated only once.

We could go a bit overboard and present our function like this:

ghci 79> let {bmiTell :: (Floating a, Ord a) ⇒ a → a → String;
			bmiTell weight height
				| bmi 6 skinny = "underweight range"
				| bmi 6 normal = "normal range"
				| bmi 6 fat = "overweight range"
				| otherwise = "obese range"
				where {
				bmi = weight / height ↑ 2;
				skinny = 18.5;
				normal = 25.0;
				fat = 30.0} }
ghci 80> bmiTell 85 1.90
"normal range"

The names we define in the where section of a function are only visible to that function, so we don’t have to worry about them becoming part of the namespace of other functions.

But overusing where bindings might decrease the readability of our function instead of increasing it – it’s just like overusing footnotes/endnotes in a paper.

Importantly, where bindings aren’t shared across function bodies of different patterns. If we want several patterns of one function to access some shared name, we have to define it globally with a let binding (see next section).

Note that when we don’t work in ghci, all the names declared in a where block have to be aligned in the exact same way. If we don’t align them, Haskell gets confused because it doesn’t know they’re all part of the same block.

Pattern matching in where bindings :

We can also use pattern matching in where bindings. For example, we could have rewritten the where section of our previous function as:

ghci 81> let {bmiTell :: (Floating a, Ord a) ⇒ a → a → String;
		bmiTell weight height
			| bmi 6 skinny = "underweight range"
			| bmi 6 normal = "normal range"
			| bmi 6 fat = "overweight range"
			| otherwise = "obese range"
			where {
			bmi = weight / height ↑ 2;
			(skinny, normal, fat) = (18.5, 25.0, 30.0)} }
ghci 82> bmiTell 85 1.90
"normal range"

Another example: extracting initials :

Let’s make another fairly trivial function where we get a first and the last name and give someone back their initials

ghci 83> let {initials :: String → String → String;
		initials firstname lastname = [f ] ++ ". " ++ [l] ++ "."
		where {(f : _) = firstname;(l: _) = lastname} }
ghci 84> initials "John" "Doe"
"J. D."

We could have done this pattern matching directly in the function’s parameters – it’s shorter and clearer actually, see below. But we wanted to show that it’s possible to do it in where bindings as well.

ghci 85> let {initials' :: String → String → String;
		initials' [email protected](f : _) [email protected](l: _) = [f ] ++ ". " ++ [l] ++ "."}
ghci 86> initials' "John" "Doe"
"J. D."

We can even drop the as-patterns without any serious loss in readability:

ghci 87> let {initials" :: String → String → String;
		initials" (f : _) (l: _) = [f ] ++ ". " ++ [l] ++ "."}
ghci 88> initials" "John" "Doe"
"J. D."

Defining functions in where bindings

Just like we’ve defined constants in where blocks, we can also define functions. Let’s make a function that takes a list of weight-height pairs and returns a list of BMIs.

ghci 89> let {calcBmis :: (Floating a) ⇒ [ (a, a)] → [a];
		calcBmis xs = [bmi w h | (w, h) �? xs]
		where bmi weight height = weight / height ↑ 2}
ghci 90> calcBmis [ (80, 1.75),(75, 1.80)]
[26.122448979591837, 23.148148148148145]

The reason we had to introduce BMI as a function in this example is that we can’t just calculate one BMI from the function’s parameters. We have to examine the list passed to the function and there’s a different BMI for every pair in there.

Finally, note that where bindings can also be nested. It’s a common idiom to make a function and define some helper function in its where clause and then to also give that function a helper function in its own where clause.

About Author :

I am Pavankumar, Having 8.5 years of experience currently working in Video/Live Analytics project.

Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions