Let and Where Bindings
Introduction: Keeping Code Clean
Welcome to Unit 5 of our Haskell journey! In previous lessons, you learned how to define pure functions, work with explicit type signatures, and handle compiler errors. Up until now, our functions have been relatively simple.
However, as you write more complex programs, packing all your math and logic onto a single line can make your code dense and difficult to read. In this lesson, we will learn how to break down complex calculations into smaller, clearly named steps. By the end of this lesson, you will know how to use local definitions to make your code cleaner, easier to read, and simpler to maintain.
Naming Steps with let ... in ...
Often, when writing a formula, it helps to calculate a few pieces of the puzzle before combining them. In Haskell, we can use a let ... in ... block to name intermediate values directly inside an expression.
Let us build a function that calculates the area of a circle. We will start by defining our type signature and the function name. As a reminder from our previous lessons, Double -> Double means this function takes a decimal number as input and returns a decimal number.
Instead of immediately putting the entire math formula on the right side of the equals sign, we will start a let block to define some smaller steps. We need the value of piValue, and we need to square our radius (r).
In the code above, let introduces our local variables. We created piValue and rSquared. These are not global variables; they only exist inside this specific calculation.
Now, we need to use these named steps to produce our final result. We do this using the in keyword.
The in keyword tells Haskell, "Now that I have defined these intermediate steps, here is the final expression I want to evaluate." By naming piValue and rSquared, the final step piValue * rSquared reads just like plain English.
The Layout Rule: Grouping with Spaces
You might have noticed something specific about how we wrote the let block in the previous example. Both piValue and rSquared are lined up perfectly on the left side.
In many programming languages, you use semicolons to end a line and curly braces to group code together. Haskell is different; it relies on the off-side rule, commonly known as the layout rule.
The layout rule means that Haskell uses your indentation (the spaces you type) to understand how your code is grouped. When you start a let block, the first character of the first definition sets the column alignment. Every subsequent definition in that block must line up exactly with that same column.
If you were to push rSquared one space to the left, Haskell would complain with a compiler error because it would think the let block had ended prematurely. Always use the spacebar to align your local variables vertically to keep the layout rule happy.
