Chapter 4

Functions

Using the example from earlier, what if we wanted a way to double anyone's money? Imagine this scenario: we have 4 different people with money in the bank

module Main where
import Prelude

johnBank = 100
benBank = 150
jerryBank = 700
tonyBank = 15 -- tony is poor :(

Now we want to have a way to ask the computer to double the money in each of their accounts. We could do this:

john2x = johnBank * 2
ben2x = benBank * 2
-- etc etc...

But this is tedious and painful. Often when writing programs, we will have hundreds or thousands of pieces of data. Surely we don't want to create by hand a double amount for every one. We're doing the same operation every time, there must be a better way! Functions my friend. Here's an example of a function declaration (i.e creating a function)

doubleTheBank money = money * 2

This looks similar to a variable declaration, but a little different as well. Let's compare them side by side

-- Variable declaration
theBank = 400

-- Function Declaration
doubleTheBank money = money * 2

doubleTheBank appears to have a second name, money, appearing before the equals sign, what is this? It's called a function parameter, you can think of it as a placeholder; let me explain. When you create a variable like theBank = 400, we immediately assign the value 400 to it. But in doubleTheBank money = money * 2, money doesn’t have a value. Without a value assigned to money, we can’t use doubleTheBank!

So how do we assign it a value? When we use the function, we put a value or variable in the spot where the name money was; money gets assigned that value, then our function comes to life!

-- If we have this
   doubleTheBank money = money * 2
-- then,
-- doubleTheBank 400   = 400   * 2
-- doubleTheBank 20    = 20    * 2
-- doubleTheBank 600   = 600   * 2

Putting different numbers in the place of money assigns that value to it! So then, with all of that said, let us show a complete example of using our function

doubleTheBank money = money * 2
-- Below, we use doubleTheBank, putting 400 in the spot where money was
answer = doubleTheBank 400
-- answer = 400 * 2 = 800

The value that comes back from a function is called it's return value. Functions aren't named values the way variables are, but they return a value back to you based on it's definition.

Here are a few more examples of ways we could use doubleTheBank

doubleTheBank money = money * 2
lunchMoney = 20
answer = doubleTheBank lunchMoney
-- answer = lunchMoney * 2 = 20 * 2 = 40

extraCash = doubleTheBank (50+10)
-- extraCash = (50+10) * 2 = 60 * 2 = 120

quadruple = doubleTheBank (doubleTheBank 200)
-- quadruple = (doubleTheBank 200) * 2 = (200*2) * 2 = 400 * 2 = 800

What's really happening here? It turns out money is actually a variable! But it only exists within the scope of the doubleTheBank function. What’s scope? Let's talk about that.

Scope

Scope is the boundaries in which a variable exists. For example

module Main where
import Prelude

coolNumber = 20
otherNum = coolNumber + 10

multNumber num = num * coolNumber

In the above code block, coolNumber exists within the entire Main module. Notice we can use it in other places such as when defining otherNum and the multNumber function. However, the function parameter num only exists within the multNumber function. If we tried to use num outside of multNumber, we'll get an error

E.g:

doubleTheMoney money =
  -- money exists here
  money * 2

-- money doesn't exist here
answer = money * 2

Scope also gives us some naming rules. Each variable name can only be used once within it's scope. So for example, once we create the variable money within our module, we can't use that same name again.

-- declare the variable money
money = 400

-- try to declare it again.
money = 250

-- The compiler will think you're trying to change the value of money and complain.
-- Don't play with the value of money, it's bad for the economy

But because function parameters only exists within the scope of the function, you can reuse parameter names as often as you wish

-- This someNum only exists in add10
add10 someNum = someNum + 10

-- This is ok because it's a different scope from add10
multiply15 someNum = someNum * 15

And one final note on the topic. Some scopes are more universal than others. Module scope declarations (often referred to as top level) exist in more places than function scope. This means if you create a variable in the module scope, and then use that same name for a function parameter, the compiler will yelp a little bit. This situation is called shadowing.

module Main where
import Prelude

coolNum = 400

-- coolNum parameter shadows the coolNum variable declared above
addFunc coolNum = coolNum + 32

When shadowing occurs, the function will use it's parameter value over the variable declared at the top level. Shadowing is allowed but somewhat discouraged. The compiler will give you warnings not to do it.

Types of Functions

We talked about types before when declaring variables, and we can declare types for our functions as well. What's the type of our doubleTheBank function? Let's see

doubleTheBank :: Int -> Int
doubleTheBank money = money * 2

That -> is new. Whatever is to the right of the last -> is the type of the functions return value. Everything to the left of the -> are the types of the function's parameters. The types help us to make sure the function receives the correct types of parameters and uses the correct return type.

module Main where
import Prelude

-- This breaks because the function returns a String instead of an Int
brokenFunc :: Int -> Int
brokenFunc num = "hello!"

-- This definition is ok...
brokenParam :: Int -> Int
brokenParam num = num + 4

-- But this breaks because we used a string parameter instead of an Int
answer = brokenParam "cool beans!"

Once again, it's considered a best practice to add type declarations for as many of your variables and functions as is practical. Indeed, the compiler will issue warnings for any top level variables or functions that don't have accompanying type declarations.

Multi-parameter functions

Everything to the left of the -> are the types of the function's parameters.

What does this mean? It means we can use more than one function parameter!

addNums x y = x + y

What is the type of addNums? Wager a guess..?

addNums :: Int -> Int -> Int
addNums x y = x + y

How about a triple adder!?

tripleAdder :: Int -> Int -> Int -> Int
tripleAdder x y z = x + y + z

So here you see that we can essentially use as many parameters for our functions as we want! However, it's recommended to not use too many parameters for a function, otherwise the function becomes difficult to use!


Summary

Functions give us a way to run a bunch of code without having to rewrite that code over and over again.

Functions can use parameters to run the same code against different values.

Function types use the -> to separate parameters. Whatever type appears to the right of the last -> is the type of the function's return value. Everything to the left of the final -> is a parameter.

The parameters of the function only exist within the function definition, this is called scope.

Heres an example of a function declaration

coolFunc :: Int -> Int -> Int
coolFunc num1 num2 =
  num1 + num2 + 100

Self Practice

  1. Create a function that subtracts two numbers, x and y, and then multiplies them by the first number x. You subtract numbers using the - operator. Just like math, you can use ( ) to enforce order of operations

  2. Create a function that computes the square of a number

  3. Create a function that takes a Int parameter and returns a String. Hint: You dont have to do anything with the Int parameter, you can simply ignore it

Note: If you named your function or parameters differently, thats fine, as long as the function definitions are essentially the same.


Answers

Question 1:

subMult :: Int -> Int -> Int
subMult x y = (x - y) * x

Question 2:

squareNum :: Int -> Int
squareNum x = x * x

Question 3:

ignoreNum :: Int -> String
ignoreNum num = "hello!"