# Chapter 6

## Variables in functions

We've talked about creating variables in chapter 1, but these variables were created at the
*top level*. Although variables declared at the *top level* can be useful in their own right,
variables are most commonly used within functions. There are two ways to declare variables within
our functions...

`let`

bindings

The first way to create a variable in a function is using the `let`

keyword. Try out this example:

```
module Main where
import Prelude
addThenMult :: Int -> Int -> Int
addThenMult a b =
let
sum = a + b
in
sum * 2
```

As you can see above, the general form for declaring `let`

variables is as follows

```
function x =
let
name1 = value1
name2 = value2
in
name1 + name2
```

That is, first we type `let`

, followed by our variable declarations (we can have as many
declarations here as we want), followed by the `in`

keyword and finally the rest of the function.
What's happening here is that the variables declared in the `let`

statement are assigned values,
and then once we move to the lines past the `in`

keyword, we can then use those variables in our
final calculation.

Let's look at a more motivating example.

### Complex functions

Can you tell me what this function does?

```
mysteryFunction :: Number -> Number
mysteryFunction p =
p - (p * 0.24) - (p * 0.05)
```

The above function just looks like random number crunching! Even if you could deduce exactly what its doing, we haven't done ourselves any favors by typing it out this way. If we had used variables, the intent of this function would have been much clearer. Let's try that again..

```
lessMysteriousFunction :: Number -> Number
lessMysteriousFunction p =
let
price = p
couponDiscount = price * 0.24
memberDiscount = price * 0.05
in
price - couponDiscount - memberDiscount
```

Ah! Now we have a *much* better idea of what this function does. Its calculating the new price for
some item after applying a coupon and a membership discount. (If you consider 5% to be a good
discoint that is, I don't. I think you all know which store im looking at here...)

As we can see above, variables used within functions help us to clarify what our functions do.

We have another way of declaring local variables for our functions, lets look at those next.

`where`

bindings

The second way to create variables in functions is using the `where`

keyword. Lets try out the first
example again, but using `where`

this time.

```
module Main where
import Prelude
addThenMult :: Int -> Int -> Int
addThenMult a b =
sum * 2
where
sum = a + b
```

`where`

statements are a little bit simpler than the `let`

version. They use the general form

```
function x =
name1 + name2
where
name1 = value1
name2 = value2
```

And revisiting our discount calculating function, we would have the following

```
lessMysteriousFunction :: Number -> Number
lessMysteriousFunction p =
price - couponDiscount - memberDiscount
where
price = p
couponDiscount = price * 0.24
memberDiscount = price * 0.05
```

### Local Variable Type Declarations

Variables declared within functions using `let`

or `where`

can be given type declarations if you
want to add them. They're not required, but are often helpful to clarify the intent of your code.
You add type declarations to local variables in the usual way:

`let`

statement type declarations

```
module Main where
import Prelude
calculateDiscount :: Number -> Number
calculateDiscount price =
let
couponDiscount :: Number
couponDiscount = price * 0.24
memberDiscount :: Number
memberDiscount = price * 0.05
in
price - couponDiscount - memberDiscount
```

`where`

statement type declarations

```
module Main where
import Prelude
calculateDiscount :: Number -> Number
calculateDiscount price =
price - couponDiscount - memberDiscount
where
couponDiscount :: Number
couponDiscount = price * 0.24
memberDiscount :: Number
memberDiscount = price * 0.05
```

### Nested Functions!

A very inquisitive person may realize that these let and where variable declarations don't look a
whole lot different from our function declarations, and they'd be right. We can use `let`

and
`where`

statements to create functions inside of our functions too! The act of creating variables
inside of variables or functions inside of functions is called *nesting*. Using `let`

and
`where`

, we can do just that.

Lets modify our `calculateDiscount`

function to add a helper function that does the actual percent
off calculations for us:

```
module Main where
import Prelude
calculateDiscount :: Number -> Number
calculateDiscount price =
price - couponDiscount - memberDiscount
where
couponDiscount :: Number
couponDiscount = percentOff 0.24
memberDiscount :: Number
memberDiscount = percentOff 0.05
percentOff :: Number -> Number
percentOff percent = price * percent
```

### Details and Caveats

Superficially, one difference between `let`

and `where`

is that `let`

variables are declared at the
top of the function and require the `in`

keyword to specify how the variables will be used. The
`where`

statement always goes at the end of the function, and the `in`

keyword isn't used.

I also wanted to take a second to mention that `let`

and `where`

variables aren't restricted to only
the use of functions, you can also use them in variable declarations in the exact same way!

There are a couple of other strange little behaviors with regards to how exactly `let`

and `where`

variables work. I don't think its necessarily important to be bogged down with these details, but
I will leave this section here in case you're interested.

## The details

As I mentioned above, there are a couple of weird little behaviors around `let`

and `where`

variables, particularly with regards to scope. By that I mean, there are different rules for which
variables we can use when creating these local variables. This is sounding confusing, let's just
dive into the different cases.

#### 1. Function Parameters

When creating local variables using both `let`

and `where`

statements, you can use the parent
functions parameters as part of the definition.

Example:

```
module Main where
import Prelude
fn1 :: Int -> Int
fn1 theParameter =
let
-- We can use `theParameter` here in the definition of `result`
result = theParameter + 10
in
result
fn2 :: Int -> Int
fn2 theParameter =
result
where
-- Here too!
result = theParamter + 10
```

#### 2. `let`

and `where`

variables can refer to each other

You can define `let`

variables using other `let`

variables, and the same goes for `where`

variables.

```
module Main where
import Prelude
testFunc :: Int -> Int
testFunc x =
let
a = x + 10
-- b uses the variable `c` in its definition - this is ok
b = c + 10
-- c uses the variable `a` in its definition - this is also ok
c = a * 20
in
b/2
testFunc2 :: Int -> Int
testFunc2 x =
b/2
where
a = x + 10
-- b uses the variable `c` in its definition - this is ok
b = c + 10
-- c uses the variable `a` in its definition - this is also ok
c = a * 20
```

#### 3. You can use `let`

and `where`

variables at the same time

Yep, there's nothing stopping us from using both types of local variables, and this happens relatively often I'd say...

```
module Main where
import Prelude
someFunc :: Int -> Int
someFunc x =
let
a = x + 10
in
a + b
where
b = 100
```

#### 4. `Where`

variables are higher scope than `let`

You can define `let`

variables using `where`

variables, but not vice-versa. This is because
variables created using `let`

are only available for use within the `in`

statement. Since `where`

variables aren't part of the `in`

statement, they can't see or use the `let`

variables.

```
module Main where
import Prelude
-- This function is ok
goodFunction :: Int -> Int
goodFunction x =
let
a = y + 10
in
a * 2
where
y = x * 10
-- This function won't compile because `a` doesn't exist when we try to create `y`
badFunction :: Int -> Int
badFunction x =
let
a = x + 10
in
y * 2
where
y = a * 10
```

#### 5. `let`

and `where`

statements can be nested

We can use `let`

and `where`

statements in the definitions of our `let`

and `where`

variables. You
can also use `let`

statements inside of `in`

statements. That may either sound confusing or
outlandish, but these scenarios do occur as well. As usual, I will demonstrate what I mean with
examples.

```
module Main where
import Prelude
letInLet :: Int -> Int
letInLet x =
let
a =
let
-- This `b` only exists here in the definition of `a`
b = 10
in
b + x
in
-- `b` doesn't exist here. Trying to use it would cause an error
a + 10
letInWhere :: Int -> Int
letInWhere x =
-- `b` doesn't exist here. Trying to use it would cause an error
a + 10
where
a =
let
-- This `b` only exists here in the definition of `a`
b = 10
in
b + x
whereInWhere :: Int -> Int
whereInWhere x =
-- `b` doesn't exist here. Trying to use it would cause an error
a + 10
where
a =
-- This `b` only exists here in the definition of `a`
b + x
where
b = 10
whereInLet :: Int -> Int
whereInLet x =
let
a =
-- This `b` only exists here in the definition of `a`
b + x
where
b = 10
in
-- `b` doesn't exist here. Trying to use it would cause an error
a + 10
letInInStatement :: Int -> Int
letInInStatement x =
let
-- `b` doesn't exist here. Trying to use it would cause an error
a = x + 10
in
-- This `b` only exists here within the `in` statement
let
b = 20
in
a + b
```

I believe creating `let`

variables within `in`

statements actually *is* outlandish. I don't think
this is used all that often, but you are technically "allowed" to do it. I would like to make it
a point to say, you don't *have* to do any of these things if you don't want to. Stick with what
makes sense. You may likely use nested `where`

or `lets`

at some point, but don't try to use them
unncessarily; you'll know when you need to use this type of thing.

### Summary

You can declare variables and functions within your functions using `let`

and `where`

statements.
`let`

variables require the use of the `in`

keyword to get the final result of the `let`

statement.
You can also add type declarations to `let`

and `where`

variables/functions just like any other
top-level declaration. Here's an example of each

```
module Main where
import Prelude
letExample :: Int -> Int
letExample x =
let
a :: Int
a = x * 20
fn :: Int -> Int
fn num = num * 2
in
fn (a + 10)
whereExample :: Int -> Int
whereExample x =
fn (a + 10)
where
a :: Int
a = x * 20
fn :: Int -> Int
fn num = num * 2
```

Using local variables within your functions helps to clarify what the code does, so be sure to use them!

### Self Practice

**1.** The perimeter of a rectangle is calculated using the formula

2*L + 2*W

Write a function that takes 2 parameters, `length`

and `width`

, and declares two local variables
using `let`

; use one of these `let`

variables to hold the result of `2 * length`

and the next to
hold the result of `2 * width`

. Finally, use those two variables to compute the perimeter and return
the result

(**Note:** Be careful, variable names can't begin with numbers)

**2.** Repeat question 1, but use `where`

variables instead

## Answers

Question 1.

```
module Main where
import Prelude
calculatePerimeter :: Int -> Int -> Int
calculatePerimeter length width =
let
twoL = 2 * length
twoW = 2 * width
in
twoL + twoW
```

Question 2.

```
module Main where
import Prelude
calculatePerimeter :: Int -> Int -> Int
calculatePerimeter length width =
twoL + twoW
where
twoL = 2 * length
twoW = 2 * width
```