Closures and Currying
The main idea behind currying is that functions that have n
parameters can be reduced to a functions that have a single parameter.
In other words we are saying that
f : a -> b -> c
is the curried form of g : (a,b) -> c
.
Of course this means that we are saying that the two function f and g are the same or f x y = g (x,y)
.
Let us see what happens when we perform partial application of functions. In other words if a function expects 2 arguments (for example) then we can create a new function with 1 argument by partial application.
import Graphics.Element exposing (..)
add : Int -> Int -> Int
add x y =
x + y
-- create increment by partiial application (add 1)
increment : Int -> Int
increment = add 1
main = flow down [
print "add 2 3 : " (add 2 3)
,print "increment 3 gives : " (increment 3)
]
--a helper function to make display easier
print message value = show (message ++ (toString value))
In the example above we were able to create the function
increment : Int -> Int
by partial application from the function
add : Int -> Int -> Int
. As you can see the number of parameters in the function increment is less than that of the function add. note: Elm does not give us an error saying that it expected two parameters, it returns a new function with one less argument. This behavior needs getting used to as sometimes you will find that in your code if you miss a paramter the error message will be about problems caused by this new function that you have unintentionally created.
This example give us the heuristics for currying. We can read the signature add : Int -> Int -> Int
either as saying that add is a function that takes two Integers and returns an Integer or add is a function that given an Int it returns a function from (Int -> Int). And these two representations are equivalent. We just used partial application to see what this looks like.
For the most part Currying is formal and does not impact the new developer. I would say understanding partial application is quite useful in many situations.
Sections
Another related idea is that we can also use partial applications on infix operators. Here are some examples of sections:
import Graphics.Element exposing (..)
increment : Int -> Int
increment = ((+) 1)
twoToPower : Int -> Int
twoToPower = ((^)2)
square : Int -> Int
square = (flip (^)2)
main = flow down [
print "2 to the power 3 : " (twoToPower 3)
,print "increment 3 gives : " (increment 3)
,print "square of 3 is : " (square 3)
]
--a helper function to make display easier
print message value = show (message ++ (toString value))
Closures
Now we come to a very interesting concept that we must understand if we are going to pass functions to other functions.
Consider this example:
import Graphics.Element exposing (..)
createIncrementer : Int -> Int -> Int
createIncrementer n =
let incrementValue = n
in ((+) incrementValue)
incrementByOne : Int -> Int
incrementByOne = createIncrementer 1
incrementByTwo : Int -> Int
incrementByTwo = createIncrementer 2
main = flow down [
print "incrementByOne 3 : " (incrementByOne 3)
,print "incrementByTwo gives : " (incrementByTwo 3)
]
--a helper function to make display easier
print message value = show (message ++ (toString value))
In this example the function createIncrementer
returns a function. But that function depends on the local variable incrementValue
that is defined within createIncrementer
.
What is intesting is that the returned functions, incrementByOne
and incrementByTwo
remember/capture the value of this local variable at the time they were created. So they have captured some of the state of the function that they were created in. This is called "closing over the local
variable" and more generally this behavior is called a closure.
Here is a sightly more complicated example of creating a closure.
import Graphics.Element exposing (..)
type alias Employee =
{id : Int, firstName : String, lastName : String, salary: Float}
employees =
[
{id = 1, firstName = "Michael", lastName = "Jordan", salary = 5000000.0}
,{id = 2, firstName = "Kareem", lastName = "Jabbar", salary = 4000000.0}
,{id = 3, firstName = "Larry", lastName = "Bird", salary = 5000000.0}
]
-- create a closure which captueres the salary thresholdValue
employeeFilter thresholdValue =
let threshold = thresholdValue
in List.filter (\emp ->emp.salary > threshold)
filterGreaterThanMillion = employeeFilter 1000000
main =
flow down [
print "employees : " (employees)
,print "filtered employees : " (filterGreaterThanMillion employees)
]
--a helper function to make display easier
print message value = show (message ++ (toString value))