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 sum the first $ N $ terms of the Riemman Zeta function (see Wiki)

$$ \zeta(s) = \sum_{n=1}^\infty = \frac{1}{1^s} + \frac{1}{2^s} + \frac{1}{3^s} + \cdots $$

```
function sum_zeta(s,nterms)
x = 0
for n in 1:nterms
x = x + (1/n)^s
end
return x
end
```

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

```
sum_zeta(2,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_zeta(s,nterms) = sum(1/n^s for n=1:nterms)
```

We can then call it just like above:

```
sum_zeta(2,100000) # returns 1.6449240668982423
```

## Functions with optional and keyword arguments

In many cases, it can be preferable to have a function with some optional arguments. For example, let’s say we want to make the number of terms in our `sum_zeta`

an optional variable, which, naturally, will have a certain value assigned by default when left unspecified. The syntax to implement this is quire straightforward, just adding `nterms=100000`

as one of the arguments does the job:

```
sum_zeta(s, nterms=10000) = sum(1/n^s for n=1:nterms)
sum_zeta(2) # returns 1.6449240668982423
```

To make things even easier for the caller, a function can be designed to accept arguments which are identified by name rather than their position. Such arguments are known as *keyword arguments*, and their syntax is very similar to optional arguments, the only difference being that they are placed after a semicolon in the function definition. In the present example, we could make `nterms`

a keyword argument:

```
sum_zeta(s; nterms=10000) = sum(1/n^s for n=1:nterms)
sum_zeta(2) # returns 1.6449240668982423
sum_zeta(2, nterms = 1e6) # returns 1.64493306684877
```

This is particularly useful for functions which have a large number of arguments – forcing the caller to remember their exact order can become inconvenient.

## 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 (! notation)

In Julia, values are normally not copied when they are passed to function. As a consequence, 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.

Note that not every type of variable can modified by a function when passed as an input: the variable has to be *mutable*. For example, `Arrays`

are mutable by default (see more in the Array section of this tutorial):

```
function add_one!(x)
x .= x .+ 1
end
x = [1,2,3]
add_one!(x); # x is now [2,3,4]
```

Importantly, this procedure won’t work for many variable types (which are *inmutable*). In such case, the value of a variable like `x`

will change inside the function `add_one`

, but the process won’t affect the value of `x`

outside the function.

This (bang!) notation is also used, for example, in the Plots.jl visualization library, to add more data to an existing `plot`

object (see the section about Plotting).

## 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 )
```

## Storing and calling functions in a separate file

In order to put our functions in a separate file, we first create a new `myFunctions.jl`

file, where we only add our functions

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

and then we can call this from another file by using the `include`

keyword. Then, Julia then behaves exactly in the same as if we had the functions defined within our current file.

For example, let’s consider that in addition to `myFunctions.jl`

, we have another file called `test_myFunctions.jl`

with the following content

```
include("myFunctions.jl")
x = 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.