Functions

This notebook shows how to define functions with one (or several) inputs and outputs. It also shows how to use to dot (.) syntax to apply a function to each element of an array.

Loading Packages


In [1]:
using Dates

include("printmat.jl")


Out[1]:
printyellow (generic function with 1 method)

Functions with One Output

The Basic Approach

The basic approach to define a function with the name fn is

function fn(x,b)
    ...(some code)
    return y
end

Once you have defined a function, you can use it (call on it) by, for instance, y1 = fn(2,1). This will generate a y1 variable (not a y variable) in the workspace. Inside the function, x is then 2 and b is 1. Clearly, if x1=2 and b1=1, you get the same result by calling as y1 = fn(x1,b1).


In [2]:
function fn1(x,b)                   #x and b are the inputs
    c = 0.5                         #c is only "seen" inside the function
    y = b*(x-1.1)^2 - c
    return y                        #this is the output
end

y1 = fn1(2,1)
printlnPs("result from fn1(2,1): ",y1)


result from fn1(2,1):      0.310

Default Values for the Inputs

You can change the first line of the function to specify default values as

function fn(x,b=1)

In this case you can call on the function as fn(x) and the value of b will default to 1. (Clearly, inputs with default values must be towards the end of the list of inputs.)


In [3]:
function fn2(x,b=1)                 #b=1 is the default in case we call as fn2(x)
    y = b*(x-1.1)^2 - 0.5
    return y
end

printlnPs("result from fn2(2,1) and fn2(2): ",fn2(2,1),fn2(2))


result from fn2(2,1) and fn2(2):      0.310     0.310

Elementwise Evaluation

To apply the function to each element of arrays x and b, use the dot syntax (broadcasting):

y = fn.(x,b)

This calculates fn(x[i],b[i]) for each pair (x[i],b[i]).

Instead, with fn.(x,2), you calculate fn(x[i],2) for each element x[i].


In [4]:
x1 = [1,1.5]
b1 = [2,2]

println("\nresult from fn2.(x1,2): ")
printmat(fn2.(x1,2))

println("\nresult from fn2.(x1,b1): ")
printmat(fn2.(x1,b1))


result from fn2.(x1,2): 
    -0.480
    -0.180


result from fn2.(x1,b1): 
    -0.480
    -0.180

Elementwise Evaluation over Some Inputs (extra)

To apply the function to each element of the array x, but keep b fixed, use :

y = fn.(x,Ref(b))

For instance, fn301.(x1,Ref(b1)) in the next cell will create the 2-element vector

1 + (10+20+30)
1.5 + (10+20+30)

In [5]:
function fn301(x,b)
    y = x + sum(b)
    return y
end

x1 = [1,1.5]
b1 = [10,20,30]

println("result from fn301.(x1,Ref(b1)):")
printmat(fn301.(x1,Ref(b1)))


result from fn301.(x1,Ref(b1)):
    61.000
    61.500

Short Form (extra)

We can also use short forms of a function as in the cell below.

The first version (fn3) is just a single expression. It could span several lines. The second version (fn3b) is a sequence of expressions (a "compound expression") separated by semicolons (;). The last expression is the function output.


In [6]:
fn3(x,b) = b*(x-1.1)^2 - 0.5           #short form of a function

fn3b(x,b) = (c = 0.5;b*(x-1.1)^2 - c)  #this works too. Notice the ;

printlnPs("result from fn3(1.5,1) and fn3b(1.5,1): ",fn3(1.5,1),fn3b(1.5,1))


result from fn3(1.5,1) and fn3b(1.5,1):     -0.340    -0.340

Explicit Names of the Inputs: Keyword Arguments

You can also define functions that take keyword arguments like in

fn(x;b,c)

Notice the semi-colon (;). You can also specify default values as fn(x;b=1,c=0.5)

In this case, you call on the function by fn(x,c=3,b=2) or just fn(x) if you want to use the default values. This helps remembering/interpreting what the arguments represent. When calling on the function, you can pass the keyword arguments in any order and you can use comma (,) instead of semi-colon (;).


In [7]:
function fn4(x;b=1,c=0.5)
    y = b*(x-1.1)^2 - c
    return y
end

printlnPs("result from fn4(1,c=3,b=2): ",fn4(1,c=3,b=2))


result from fn4(1,c=3,b=2):     -2.980

Variable Number of Inputs (extra)

We can use the x... syntax on the last non-keyword input to capture a variable number of inputs.

For instance, fn5(-9,1,2,b=10) in the next cell will create the 2-element vector

-9 + 1*10
-9 + 2*10

In [8]:
function fn5(a,x...;b=1)   #a is traditional input, x... captures several inputs; b=1 is a keyword input
    n = length(x)
    y = zeros(n)
    for i = 1:n
        y[i] = a + x[i]*b
    end
    return y
end

y = fn5(-9,1,2,b=10)
println("result from fn5(-9,1,2,b=10): ")
printmat(y)


result from fn5(-9,1,2,b=10): 
     1.000
    11.000

Functions with Several Outputs

Several Outputs 1: Basic Approach

A function can produce a "tuple" like (y1,y2,y3) as output.

In case you only want the first two outputs, call as (y1,y2,) = fn(x).

Instead, if you only want the second and third outputs, call as (_,y2,y3) = fn(x)

You can also extract the second output as y2 = fn(x)[2]

If Y1 is an already existing array, then (Y1[3],y2,) = fn(x) will change element 3 of Y1 (and also assign a value to y2).


In [9]:
function fn11(x,b=1)
    y1 = b*(x-1.1)^2 - 0.5
    y2 = b*x
    y3 = 3
    return y1, y2, y3
end

(y1,y2,) = fn11(1,2)
printlnPs("The first 2 outputs from fn11(1,2): ",y1,y2)

y2 = fn11(1,2)[2]         #grab the second output
printlnPs("\nThe result from calling fn11(1,2)[2]: ",y2)

Y1 = zeros(5)             #create an array
(Y1[3],y2,) = fn11(1,2)   #put result in existing array
printlnPs("\nY1 array after calling fn11(1,2): ",Y1)


The first 2 outputs from fn11(1,2):     -0.480         2

The result from calling fn11(1,2)[2]:          2

Y1 array after calling fn11(1,2):      0.000     0.000    -0.480     0.000     0.000

Several Outputs 2: Named Tuples and Dictionaries (extra)

Instead of returning several values, it might be easier to combine them into either a "named tuple" or a dictionary and then exporting that.


In [10]:
function fn12(x,b=1)
    y1 = b*(x-1.1)^2 - 0.5
    y2 = b*x
    y3 = 3
    y  = (a=y1,b=y2,c=y3)                     #named tuple
    return y
end

y1 = fn12(1,2)
printlnPs("from fn12(1,2): ",y1.a,y1.b)      #y1.a to get "element" a of the output y1


from fn12(1,2):     -0.480         2

In [11]:
function fn13(x,b=1)
    y1 = b*(x-1.1)^2 - 0.5
    y2 = b*x
    y3 = 3
    y  = Dict("a"=>y1,"b"=>y2,"c"=>y3)         #dictionary
    return y
end

y1 = fn13(1,2)
printlnPs("from fn13(1,2): ",y1["a"],y1["b"])  #y1["a"] to get "element" a of the output y1


from fn13(1,2):     -0.480         2

Several Outputs 3: Elementwise Evaluation (extra)

...can be tricky, because you get an array (same dimension as the input) of tuples instead of a tuple of arrays (which is probably what you want).

One way around this is to reshuffle the output to get a tuple of arrays.

Alternatively, you could loop over the function calls or write the function in such a way that it directly handles array inputs (without the dot). The latter is done in fn14().


In [12]:
y = fn11.([1,1.5],2)
println("y, a 2-element vector of tuples (3 elements in each):")
printmat(y)

(y1,y2,y3) = ntuple(i->getindex.(y,i),3)    #split up into 3 vectors

println("\nthe vectors y1 and y2:")
printmat([y1 y2])


y, a 2-element vector of tuples (3 elements in each):
(-0.48, 2.0, 3)
(-0.18000000000000016, 3.0, 3)


the vectors y1 and y2:
    -0.480     2.000
    -0.180     3.000


In [13]:
function fn14(x,b=1)                 #x can be an array
    y1 = b*(x.-1.1).^2 .- 0.5
    y2 = b*x
    y3 = 3
    return y1, y2, y3
end

(y1,y2,) = fn14(x1,2)               #function written to handle arrays
println("result from fn14(x1,2): ")
printmat([y1 y2])


result from fn14(x1,2): 
    -0.480     2.000
    -0.180     3.000

Documenting Your Function

To use Julia's help function (? FunctionName), put the documentation in triple quotes, just above the function definition. (No empty lines between the last tripe quote and the start of the function.) The cell below illustrates a simple case.


In [14]:
"""
    fn101(x,b=1)

Calculate `b*(x-1.1)^2 - 0.5`.

# Arguments
- `x::Number`:    an important number
- `b::Number`:    another number

"""
function fn101(x,b=1)
    y = b*(x-1.1)^2 - 0.5
    return y
end


Out[14]:
fn101

In [15]:
? fn101


search: fn101

Out[15]:
fn101(x,b=1)

Calculate b*(x-1.1)^2 - 0.5.

Arguments

  • x::Number: an important number
  • b::Number: another number

Sending a Function to a Function

You can use a function name (for instance, cos) as an input to another function.


In [16]:
function fn201(f,a,b)   #f is a function, could also write f::Function
    y = f(a) + b
    return y
end

#here f is the cos function
printlnPs("result from fn201(cos,3.145,10): ",fn201(cos,3.145,10))  

#here f is z->mod(z,2), an anonymous function
printlnPs("\nresult from fn201(z->mod(z,2),3.145,10):",fn201(z->mod(z,2),3.145,10))


result from fn201(cos,3.145,10):      9.000

result from fn201(z->mod(z,2),3.145,10):    11.145

In [ ]: