First-class functions and closures

In this section, we will demonstrate the power and flexibility of functions (example code can be found in Chapter 3first_class.jl). Firstly, functions have their own type: Function. Functions can also be assigned to a variable by their name:

julia> m = mult  
julia> m(6, 6) #> 36

This is useful when working with anonymous functions, such as c = x -> x + 2, or as follows:

julia> plustwo = function (x) 
                     x + 2 
                 end 
(anonymous function) 
julia> plustwo(3) 
5 

Operators are just functions written with their arguments in an infix form; for example, x + y is equivalent to +(x, y). In fact, the first form is parsed to the second form when it is evaluated. We can confirm it in the REPL: +(3,4) returns 7 and typeof(+) returns Function.

A function can take a function (or multiple functions) as its argument, which calculates the numerical derivative of a function f; as defined in the following function:

function numerical_derivative(f, x, dx=0.01) 
    derivative = (f(x+dx) - f(x-dx))/(2*dx) 
    return derivative 
end 

The function can be called as numerical_derivative(f, 1, 0.001), passing an anonymous function f as an argument:

f = x -> 2x^2 + 30x + 9 
println(numerical_derivative(f, 1, 0.001)) #> 33.99999999999537  

A function can also return another function (or multiple functions) as its value. This is demonstrated in the following code, which calculates the derivative of a function (this is also a function):

function derivative(f) 
    return function(x)   
  # pick a small value for h 
        h = x == 0 ? sqrt(eps(Float64)) : sqrt(eps(Float64)) * x 
        xph = x + h 
        dx = xph - x 
        f1 = f(xph) # evaluate f at x + h 
        f0 = f(x) # evaluate f at x 
        return (f1 - f0) / dx  # divide by h 
    end 
end 

As we can see, both are excellent use cases for anonymous functions.

Here is an example of a counter function that returns (a tuple of) two anonymous functions:

function counter() 
    n = 0 
    () -> n += 1, () -> n = 0 
end 

We can assign the returned functions to variables:

  (addOne, reset) = counter() 

Notice that n is not defined outside the function:

julia> n 
ERROR: n not defined 

Then, when we call addOne repeatedly, we get the following output:

addOne() #=> 1 
addOne() #=> 2 
addOne() #=> 3 
reset()  #=> 0 

What we see is that, in the counter function, the variable n is captured in the anonymous functions. It can only be manipulated by the functions, addOne and reset. The two functions are said to be closed over the variable n and both have references to n. That's why they are called closures.

Currying (also called a partial application) is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument. Here is an example of function currying:

function add(x) 
    return function f(y) 
        return x + y 
    end 
end 

The output returned is add (generic function with 1 method).

Calling this function with add(1)(2) returns 3. This example can be written more succinctly as add(x) = f(y) = x + y or, with an anonymous function, as add(x) = y -> x + y. Currying is especially useful when passing functions around, as we will see in the Map, filter, and list comprehensions section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.159.223