In any language, the function is one of the most important ideas and this chapter is a review of functions with examples in julia. The Julia documentation on functions is a great place for details.
A simple example of a julia function is
function sq(x)
x*x
end
which just returns the square of the argument, called x
. An alternative way to do this is the following:
f(x)=x^2
To call a function, it is much like that of any other language. If we type:
sq(3)
This returns 9, the square of 3 and f(-4)
returns 16.
The function arguments are the numbers or strings or any element that is passed into the function. In the function sq
above, the number x
is the only argument. We can have more arguments by separating by commands. The (quite unnecessary function) theSum
will take two arguments and add the result:
function the_sum(x,y)
x+y
end
the_mean
that returns the mean (average of 2 numbers)In the function sq
or the_sum
the returned value is the last line of the function. If you want to return a value before the last line you can use the return
command. The following will return true if the number is odd and false if the number is true:
function is_odd(n)
if mod(n,2)==1
return true
end
false
end
The mod
function is the remainder of n
divided by 2. If the remainder is 1, then the number is odd. On the line return true
the code stops here and exits the function and doesn’t execute any of the other lines.
Note: the ==
tests for equality. This will be discussed later in the course.
A better way to write this (but doesn’t use the return statement) just evaluates if mod(n,2)
is 1 or not:
function is_odd(n)
mod(n,2)==1
end
which will return true
if mod(n,2)
is actually 1 and false
if it is anything else (but the only other possibility is 0).
The isOdd
function above should only work on integers, but if type isOdd(3.5)
you get a result. (But does it give you want you want?). What if you put in a string like “hi”?
If instead, you specify a type in the following way:
function is_odd(n::Integer)
mod(n,2)==1
end
and restart the kernel (either in Jupyter using Kernel->Restart. We’ll talk later about why you need to do this). Check and see what happens if you try isOdd
on a non integer or a string.
the_mean
function from above using a type for the argument. Since both floats and integers are numbers, use the Number
type for this. Restart the kernel and test your function using both numbers (floats and integers or rationals) and non numbers (like a string).Before starting this, make sure that you have done the exercise above to write a 2-argument version of the mean and it is currently in the kernel (this means, just run it again, if in doubt.)
A three-argument mean function can be written
function the_mean(x::Number,y::Number,z::Number)
(x+y+z)/3
end
Depending on the number of arguments, julia will call the appropriate function. This is an example of Multiple Dispatch, in which either the number or type of arguments determine the actual function call.
If you look back at your document when you declared the functions, you should see that the second one entered said the_mean (generic function with 2 methods)
, which says that there are two functions called mean. Typing methods(the_mean)
results in:
the_mean is a Function.
# 2 methods for generic function "mean":
the_mean(x::Number, y::Number) in Main at In[5]:2
the_mean(x::Number, y::Number, z::Number) in Main at In[6]:2
although you will get different line numbers for the In[??]:2
. The important thing is that there are two function definitions.
Multiple dispatch also allows different types of arguments as well. Let’s say we want to create a function mean
that take a single string as a argument like:
function the_mean(str::String)
string("This should return the definition of ",str)
end
and entering it shows you that there are now 3 functions. Try typing mean("definition")
.
From the last exercise, it would be unfortunate if we have to write different functions for different number of arguments. We can write a variable number of arguments with a … trailing the last argument. The following is a generalized version of the mean:
function the_mean(x::Number...)
local i
local sum=0
for i=1:length(x)
sum+=x[i]
end
sum/length(x)
end
which uses a for loop that we will discuss later. This function will now find the mean using any number of arguments. Try the_mean(1,2,3)
and the_mean(1,2,3,4,5)
and determine if it is returning what you expect.
A very nice feature of Julia functions is that of multiple return values. Instead of only being able to return a single number (or requiring to send an array or structural type), you can return more than 1 number (or other data type). For example.
function h(x,y)
x+y,x-y
end
and if you call this, say h(3,5)
you will get the result (8,-2)
or if you say
p,q=h(3,5)
the p will take on the value 8 and q will be equal to -2.
Mathematically, we define the factorial as a function on a non-negative integer as
\[ n! = n(n-1)(n-2)\cdots 3 \cdot 2 \cdot 1 \]
or the product of all of the numbers from itself down to 1. There are a number of ways to program this function as we will see. The following is a way using a for
loop
function fact(n::Integer)
local prod=1
for i=1:n
prod *= i
end
prod
end
We store the result (product) in a variable called prod and multiply all of the numbers up to n
.
Another type of function is one that calls itself and is called a recursive function. One of the standard examples of this is the factorial function.
Above, we saw how to compute the factorial of a number using a for loop. There’s another way to do this. We can define the factorial in the following way:
$$ n!=\begin{cases}1&n=0\newline n\cdot(n-1)!&\text{otherwise}\end{cases}$$
The big difference is that inside the function is a call to the function. This is what is called a recursive function. Often this is very helpful and the factorial is a great example of this. We can write the factorial this way in the following:
function factr(n::Integer)
if n == 0
return 1
else
return n*factr(n-1)
end
end
where n == 0
tests if the variable n
is 0 or not.
A common recursive function is that of a Fibonacci number. If we let $f(0)=1$, $f(1)=1$, and then
$$ f(n)=f(n-1)+f(n-2)$$. Write a recursive function that produces fibonacci numbers. Test it on small values of $n$.
If you are using the command line julia (also called the REPL) or iJulia, then entering:
x=6
will create x
as a global variable, even without saying so explicitly. If we consider the factorial function above:
function fact(n::Integer)
prod=1
for i=1:n
prod *= i
end
prod
end
then recall that n
is the function argument and the variable prod
is actually declared locally implicitly and it is good form to say it is local by
function fact(n::Integer)
local prod=1
for i=1:n
prod *= i
end
prod
end
The variable prod
is not defined or visible outside the function. If we type 2*prod
for example, an error will occur saying that prod
is not defined.
If we have:
x=3
function f()
local x=2
x
end
f()
then this will return 2 and typing x
(to see it’s value) returns 3, indicating that there are two different x
variables.