Table of content

Let bindings in Haskell

A let binding is very similar to a where binding. A where binding is a syntactic construct that binds variables at the end of a function and the whole function (or a whole pattern-matching subpart) can see these variables, including all the guards

A let binding binds variables anywhere and is an expression itself, but its scope is tied to where the let expression appears. So if it’s defined within a guard, its scope is local and it will not be available for another guard. But it can also take global scope overall pattern-matching clauses of a function definition if it is defined at that level.

The form is let bindings in expression. The names that you define in the let part are accessible to the expression after the in part. For example, this is how we could define a function that gives us a cylinder’s surface area based on its height and radius:

ghci 91> let {cylinder :: (Floating a) ⇒ a → a → a;
		cylinder r h =
		let sideArea = 2 ∗ pi ∗ r ∗ h; topArea = pi ∗ r ↑ 2
		in sideArea + 2 * topArea}
ghci 92> cylinder 2 7

The names should be aligned in the same way when we do not use ghci, i.e., when we do not add the curly braces and the semicolon to explicitly indicate the let bindings block.

We could have also defined the cylinder function with a where binding. So what are the main differences between let and where bindings?

  • let puts the bindings first and the expression that uses them later, whereas where is the other way around
  • more importantly, let bindings are expressions themselves while where bindings are just syntactic constructs that cannot be interpreted on their own

Recall that when we discussed the if statement, we said that an if then else statement is an expression and it can occur almost anywhere, for example

ghci 93> [if 5 > 3 then "Woo" else "Boo", if ’a’ > ’b’ then "Foo" else "Bar"]
["Woo", "Bar"]
ghci 94> 4 * (if 10 > 5 then 10 else 0) + 2

They can also be used to introduce functions in embedded expressions (in which case the function names have local scope):

ghci 96> [let square x = x * x in (square 5,square 3,square 2)]
[ (25, 9, 4)]

If we want to bind several variables inline, we can separate them with semicolons

ghci 97> let a = 100; b = 200; c = 300 in a * b * c
ghci 98> let foo = "Hey "; bar = "there!" in foo ++ bar
"Hey there!"

We don’t have to put a semicolon after the last binding, but we can

ghci 99> let a = 100; b = 200; c = 300; in a * b ∗ c
ghci 100> let foo = "Hey "; bar = "there!"; in foo ++ bar
"Hey there!"

Similarly, we can but often do not have to enclose the bindings with curly braces (although we’ve been doing this up until now).

Pattern matching with let bindings

Just like any construct in Haskell that is used to bind values to names, we can pattern match with let bindings. E.g., we can dismantle a tuple into components and bind the components to names.

ghci 101> let (a, b, c) = (1, 2, 3)
ghci 102> a
ghci 103> b
ghci 104> c

We can do all this inside another expression since let itself is an expression and it can be freely embedded.

ghci 105> (let (a, b, c) = (1, 2, 3) in a + b + c) * 100

let bindings in list comprehensions :

We can also put let bindings inside list comprehensions. Let’s rewrite our previous example of calculating lists of weight-height pairs to use a let inside a list comprehension instead of defining an auxiliary function with a where.

ghci 106> let {calcBmis :: (Floating a) ⇒ [ (a, a)] → [a];
calcBmis xs = [bmi | (w, h) " xs, let bmi = w / h ↑ 2]}

ghci 107> calcBmis [ (80, 1.87),(63, 1.62)]
[22.877405702193368, 24.005486968449926]

We include a let inside a list comprehension much like we would a predicate – only it doesn’t filter the list, it just introduces a new binding. The names defined in a let inside a list comprehension are visible to the output function (the part before the |) and all predicates and sections that come after of the binding So we could make our function return only BMIs in the overweight and obese ranges:

ghci 108> let {calcBmis :: (Floating a) ⇒ [ (a, a)] → [a];
calcBmis xs = [bmi | (w, h) " xs, let bmi = w / h ↑ 2, bmi > 25.0]}

We omit the in part of a let binding when we use it in a list comprehension because the visibility of the binding is already predefined there (but we could use a let in binding inside a predicate in a list comprehension and the names would only be visible to that predicate).

The in part can also be omitted when defining functions and constants directly in ghci, like we’ve been doing all this time. When we do that, the names are visible throughout the entire interactive session.

ghci 109> let zoot x y z = x * y + z

ghci 110> zoot 3 9 2

ghci 111> let boot x y z = x *y + z in boot 3 4 2

ghci 112> boot

Should we use let bindings all the time instead of where bindings? (there are functional programming languages that have only let constructs). There are two kinds of situations in which where bindings are preferable:

  • where bindings have scope across guards (but inside a pattern-matching clause) automatically
  • where bindings define helper names/functions after the main function, so the main function’s body is closer to its name and type declaration, which increases readability
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