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
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 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 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
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.
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
-> 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.
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!
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
Create a function that subtracts two numbers,
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
Create a function that computes the square of a number
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.
subMult :: Int -> Int -> Int subMult x y = (x - y) * x
squareNum :: Int -> Int squareNum x = x * x
ignoreNum :: Int -> String ignoreNum num = "hello!"