Tutorials
Blog
About

Tutorials > Julia Programming: a Hands-on Tutorial

Functions

by Martin D. Maas, Ph.D
@MartinDMaas

Last updated: 2021-11-06

Encapsulate your Julia code into functions, for convenience and performance.

Functions play a key role in structuring Julia code. Arguably, most programs in Julia are basically constructed by applying and composing functions.

Structuring code into functions is also key for performance. As Julia is structured around a JIT compiler, in order to get our code compiled we need to wrap it up inside a function. As a consequence, performance-critical sections of our code should always be written inside functions.

There are many ways to define and use functions in Julia. Let’s review them.

Defining a function

As an example, let’s define a function that will evaluate the first \( N \) terms of the quadratic series

function sum_series(n)
    x = 0
    for k in 1:n
        x = x + (1/k)^2
    end
    return x
end

After executing that code, we can call our function from the REPL, by typing:

sum_series(100000)      # returns 1.6449240668982423

One-line functions

In Julia, we can also define a function with a single line of code. For example, by doing:

sum_series2(N) = sum(1/n^2 for n=1:N)

We can then call it just like above:

sum_series2(100000)     # returns 1.6449240668982423

Functions with multiple outputs

An other common requirement is to be able to write functions which return multiple arguments. This can be done in Julia in a very straightforward manner.

function circle(r)
    area = π * r^2
    circumference = 2π * r
    return area, circumference
end

a, c = circle(1.5)

What is happening under the hood is that the circle function is returning a tuple, and that tuple is being destructured into two variables.

In fact, we can also call our circle function and expect a single output (a tuple), and use it as follows:

shape = circle(1.5)     # returns (7.0685834705770345, 2.356194490192345)
shape[1]                # 7.0685834705770345
shape[2]                # 2.356194490192345
a, c = shape            # destructures the tuple as in the original

Note that tuples are inmutable structures: we won’t be able to modify the values of shape. But we can modify the values of a and c.

Functions which modify their input arguments

In Julia, values are not copied when they are passed to function. In particular, a function could change the content of input arguments. To let the caller know if this is indeed the case, it’s a convention to append an exclamation mark to names of functions that do modify their arguments.

function add_one!(x)
    x = x + 1
end

x = 3
add_one!(x);    # v is now 4

This notation is also used, for example, in the Plots.jl visualization library, to add more data to an existing plot object.

Anonymous functions

Sometimes we don’t need to assign a name to a function. For example, when we need to quickly define a function, to pass it as an argument to another function.

Let’s consider the following example. Let’s say we have written (or we are using) the following code which finds the root of a given function f, with the secant method (see Wikipedia).

function secant(f,a,b,rtol,maxIters)
    iter = 0
    while abs(b-a) > rtol*abs(b) && iter < maxIters
        c,a = a,b
        b = b + (b-c)/(f(c)/f(b)-1)
        iter = iter + 1
    end
    return b
end

Of course, this code can be applied to any function. We could define a function and pass it as an argument, or use anonymous functions as a shorthand.

For example, let’s call this secant procedure to find the so-called “golden ratio”, which is the positive root of the polynomial \( x^2 - x - 1 \). We can do this by resorting to an anonymous function, as follows:

φ = secant( x-> x^2 - x - 1, 1, 2, 1e-15, 10 )

Organizing functions into modules

In order to put our function in a module and import it, we first create a new ``myModule.jl’‘ file, with the following content

module myModule

export sum_series

function sum_series(n)
    x = 0
    for k in 1:n
        x = x + (1/k)^2
    end
    return x
end

end

and then we can call it from an other file, by doing

include("myModule.jl")

Then, our module will get compiled and available. We can access the exported function, by doing

x = myModule.sum_series(100000)

Conclusion

This post covered the basics of how to structure computations in Julia with functions. There is a lot more to learn in this topic! Stay tuned as this is a growing and evolving series of tutorials.

Ask me a question or send me your comments!

Don't hesitate to ask me any question about the topics I cover on this blog!

Click here to reach out!