Julia is a functional language. Given specific information (called arguments), a function is a keyword that executes a task according to rules designed specifically for that function. Think of arithmetical addition as a task (a function) and the values to be added as the arguments. The term multiple dispatch refers to calling the right implementation of a function based on the arguments. Note that only the positional arguments are used to look up the correct method. When the function is used again, but with different argument types, a new method is selected. This is called overloading.
While we would usually think of the task of addition as a single task, adding numbers, it can in fact be seen as more than one function. One with rules for adding integers, one for adding real numbers, one for adding complex numbers, and so on. So, when we call a function (typing the specific keyword and adding the arguments is referred to as calling the function), we actually call a whole buch of them. Julia decides which one it is going to use based on the argument types (there is a lookup table for every function, which is stored with the function). Julia generates low-level code based on your computer's instruction set. So, when you create a function () such as...
function cbd(a)
return a^3
end
... a whole bunch of methods are created (the different implementations of a function are called methods). When the function is called with an integer argument, Julia will generate code that uses the CPU's integer multiplication instruction set and when a floating point value is used, the floating point multiplication instruction set will be targeted.
Let's have a look at a quintessential Julia function. You might not recognize it at first, but typing 2 + 3
is actually converted to a keyword with arguments when executed.
# Adding 2 and 3
2 + 3
5
The +
symbol is actual a function name. The typical architecture of a Julia function is then a keyword, with a set of arguments, seperated by commas, all inside of a set of parenthesis.
# Addition as a function
+(2, 3)
5
Functions in Julia can be created much like a mathematical function. Below we create a function called f
that takes a single argument. We use the character x
as placeholder argument. The right-hand side of the equation stipulates the task that we want the function to perform, given a value for the argument.
# A function to square the argument value
f(x) = x^2
f (generic function with 1 method)
methods(f)
We can no call the function and provide an argument.
# Squaring 10
f(10)
100
The answer is $100$ as expected.
Whilest our function seems algebraic in nature, we can create a similar function that will act on a string.
# Creating a function to print input to the screen
p(x) = println(x, " was entered!") # The comma concatenates the two strings
p (generic function with 1 method)
If we now pass a string as argument and see the result.
# Passing the string "Julia"
p("Julia")
Julia was entered!
We can use more than one argument too.
# Simple replication of the + function for two arguments
g(x, y) = x + y
g (generic function with 1 method)
Passing two numbers as arguments now adds the two values.
g(3, 4)
7
With single expression functions, it was convenient to use the shortcut (almost mathematical) syntax we used above. If we want a function to do a few more things, even have flow control, we have to use function syntax. In the first example below we will have a function that takes two arguments and performs two tasks (has two expressions).
The creation of such a proper Julia function is achieved using the function
keyword. this is followed by the name given to our new function. It is important to stick to conventions and not use illegal words and characters. The former included reserved keywords that are already Julia functions and the latter includes leading numbers.
A list of placeholder symbols for our arguments follow. In the function below, we use two arguments. The first task we would like the function to perform is to print the two values that are entered as arguments. The second multiplies the values. All function are completed with the end
keyword.
# Declaring the block of code as a function using the function keyword, giving it a name,
# and listing the arguments
function mltpl(x, y)
print("The first value is $x and the second value is $y.\n$x x $y is:")
# The dollar signs are placeholders for the argument values
# The \n combination indicates a new-line
x * y
end
mltpl (generic function with 1 method)
mltpl(3, 4)
The first value is 3 and the second value is 4. 3 x 4 is:
12
The return
keyword can be used to force a halt to the taks being performed. It is not immediately obvious how this can be helpful. Below is a demonstartion. (An example that shows the usefulness of the return
keyword is shown in Flow control in a function below.)
# The expression (task) after the return keyword will be ignored
function mltpl_return(x, y)
print("The first value is $x and the second value is $y.\n$x x $y is:")
# The dollar signs are placeholders for the argument values
# The \n combination indicates a new-line
return x * y
x + y # Adding addition of the two argument values after the return keyword
end
mltpl_return (generic function with 1 method)
mltpl_return(3, 4)
The first value is 3 and the second value is 4. 3 x 4 is:
12
The omission of the return
keyword can lead to some unexpected behaviour. Below, we print a line in the first expression, than successively add, subtract, and multiply the two argument values.
function omit_return(x, y)
println("The argument values that were passed are $x and $y")
x + y
x - y
x * y
end
omit_return (generic function with 1 method)
omit_return(3, 4)
The argument values that were passed are 3 and 4
12
Only the println()
expression and the last expression were executed. We can correct this as shown below.
function multiple_return(x , y)
println("The argument values that were passed are $x and $y")
x + y, x - y, x * y
end
multiple_return (generic function with 1 method)
multiple_return(3, 4)
The argument values that were passed are 3 and 4
(7, -1, 12)
We see the result of the arithmetical operations are returned as a tuple. This can be useful as we can assign a computer variable name to each of the elements in the tuple.
ans1, ans2, ans3 = multiple_return(3, 4)
The argument values that were passed are 3 and 4
(7, -1, 12)
# Calling the value in ans1
ans1
7
ans2
-1
ans3
12
A function can have flow control, i.e. if-else
statements as tasks. Below is an example that also makes the benefits of the use of the return
keyword more obvious.
The aim of the function is to return the absolute value of the difference between two numbers, without the use of the abs()
function. The latter returns the absolute value of a value.
function abs_diff(x, y)
if x >= y
return x - y
end
return y - x
end
abs_diff (generic function with 1 method)
# The absolute value of 4 - 3
abs_diff(4, 3)
1
# The absolute value of 10 - 12
abs_diff(10, 12)
2
Optional arguments can be passed as arguments when a function is being created. These are provided with default values. When they are not used when calling the function, these default values are used. They can be overwritten when the argument is called, though.
function func(a, b, c = 100)
print(" We have the values $a, $b, and $c.")
end
func (generic function with 2 methods)
When omitting to provide the third argument, the default of $100$ is used.
# Omitting the third argument
func(10, 20)
We have the values 10, 20, and 100.
Below, we provide a different value to the third argument.
func(10, 20, 1000)
We have the values 10, 20, and 1000.
We can create function with many, many argument. Problem is, we might forget the argument order when calling the function and passing values to it. To solve this problem the semi-colon (;) can be used (usually after the ordered arguments). Let's take a look.
# A most ridiculously long print statement (apologies)
function func2(a, b, c = 100 ; p = 100, q = "red")
println("The first ordered argument value is $(a).")
println("The second ordered argumnent is $(b).")
println("The third ordered argument was optional.")
println("If you see a value of 100 here, you either passed a value of 100 or omitted it: $(c).")
println("Let's see what happend to the keyword p: $(p).")
println("Let's see what happens to the keyword q: $(q).")
println("Oh yes, let's also return something useful, like multiplying $(a) and $(b), yielding:")
return a * b
end
func2 (generic function with 2 methods)
We can now call the function with just the first two arguments.
# Calling just the first two ordered arguments
func2(3, 4)
The first ordered argument value is 3. The second ordered argumnent is 4. The third ordered argument was optional. If you see a value of 100 here, you either passed a value of 100 or omitted it: 100. Let's see what happend to the keyword p: 100. Let's see what happens to the keyword q: red. Oh yes, let's also return something useful, like multiplying 3 and 4, yielding:
12
Now, let's change the default value for the third arguments and then also some of the keyword arguments.
# Calling something else for c
func2(3, 4, 5)
The first ordered argument value is 3. The second ordered argumnent is 4. The third ordered argument was optional. If you see a value of 100 here, you either passed a value of 100 or omitted it: 5. Let's see what happend to the keyword p: 100. Let's see what happens to the keyword q: red. Oh yes, let's also return something useful, like multiplying 3 and 4, yielding:
12
# Now let's have some fun with the keyword arguments
func2(3, 4, p = pi) # Using the pi Julia keyword
The first ordered argument value is 3. The second ordered argumnent is 4. The third ordered argument was optional. If you see a value of 100 here, you either passed a value of 100 or omitted it: 100. Let's see what happend to the keyword p: π = 3.1415926535897.... Let's see what happens to the keyword q: red. Oh yes, let's also return something useful, like multiplying 3 and 4, yielding:
12
# Now for q
func2(3, 4, 2, q = "Hello!")
The first ordered argument value is 3. The second ordered argumnent is 4. The third ordered argument was optional. If you see a value of 100 here, you either passed a value of 100 or omitted it: 2. Let's see what happend to the keyword p: 100. Let's see what happens to the keyword q: Hello!. Oh yes, let's also return something useful, like multiplying 3 and 4, yielding:
12
The order of the keyword arguments can now be changed when calling the function. As long as we remember to use their names.
# Mixing the keyword arguments around
func2(3, 4, 2, q = "It works!", p = exp(1))
The first ordered argument value is 3. The second ordered argumnent is 4. The third ordered argument was optional. If you see a value of 100 here, you either passed a value of 100 or omitted it: 2. Let's see what happend to the keyword p: 2.718281828459045. Let's see what happens to the keyword q: It works!. Oh yes, let's also return something useful, like multiplying 3 and 4, yielding:
12
The keyword arguments can indeed be placed anywhere, simply use their names. The values before the semicolon, though has to be used, or at least interspersed in the correct order.
# And finally, we go bananas!
func2(q = "Bananas!", 3, 4, p = sqrt(3), 2)
The first ordered argument value is 3. The second ordered argumnent is 4. The third ordered argument was optional. If you see a value of 100 here, you either passed a value of 100 or omitted it: 2. Let's see what happend to the keyword p: 1.7320508075688772. Let's see what happens to the keyword q: Bananas!. Oh yes, let's also return something useful, like multiplying 3 and 4, yielding:
12
We can use three dots, as in ..., (called a splat or ellipsis) to indicate none, one, or many arguments. Let's take a look.
function func3(args...)
print("I can tell you how many arguments you passed: $(length(args)).")
end
func3 (generic function with 1 method)
The function simply counts the number of arguments passed.
# Calling nothing, nothing, nothing. Hello! Is anyone home?
func3()
I can tell you how many arguments you passed: 0.
Below, we take a look at what happens when we pass a variety of arguments.
# Now someone's home!
func3(1000000)
I can tell you how many arguments you passed: 1.
# It's Julia!
func3("Julia")
I can tell you how many arguments you passed: 1.
# Passing two arguments
func3("Hello", "Julia")
I can tell you how many arguments you passed: 2.
# Passing multiple arguments of different types
func3("Julia", "is", 1, "in", "a", 1000000, "!")
I can tell you how many arguments you passed: 7.
The splat or ellipsis as indicator of allowing the use of multiple (infinite) arguments, can solve some problems. In the example below we will pass a list of strings as arguments and see what happens.
# A functions that joins strings
function surgery(string_array)
string_items = join(string_array, ", ", " and ") # Creating a computer variable to hold
# the arguments and concatenate a comma and the word and
print("Today I performed the following operations: $string_items", "!")
end
surgery (generic function with 1 method)
# Passing two arguments
surgery(["colonic resection", "appendectomy"])
Today I performed the following operations: colonic resection and appendectomy!
# What if I forget the square brackets []
# The join() function will act on the characters in the string
surgery("appendectomy")
Today I performed the following operations: a, p, p, e, n, d, e, c, t, o, m and y!
# Now we don't restrict the number of arguments
function splat_surgery(stringsss...)
string_items = join(stringsss, ", ", " and ")
print("Today I performed the following operations: $string_items", "!")
end
splat_surgery (generic function with 1 method)
splat_surgery("appendectomy")
Today I performed the following operations: appendectomy!
For the sake of clarity, look at the following example to see what Julia does to the args... arguments. You will note that it is actually managed as a tuple.
function argues(a, b, s...)
print("The argument values are: $a, $b, and $s")
end
argues (generic function with 1 method)
# The first two values, 3 and 4, have proper assignment, but the rest will be in a tuple
argues(3, 4, 5, 6, 7, 8, "Julia")
The argument values are: 3, 4, and (5, 6, 7, 8, "Julia")
# Now for an empty tuple
argues(3, 4)
The argument values are: 3, 4, and ()
Now for some real fun. We can combine keywords and splats. Have a look at this.
# Creating a function that only contains keywords, but they are
# splats
function fun_func(; a...)
a
end
fun_func (generic function with 1 method)
# Calling the fun_func() function, remembering to give the keywords names
fun_func(var1 = "Julia", var2 = "Language", val1 = 3)
pairs(::NamedTuple) with 3 entries: :var1 => "Julia" :var2 => "Language" :val1 => 3
We now have a collection of (key, value) tuples, with the key coming from the name we gave the keyword argument. Moreover, it is actually a symbol which you will note by the colon (:) preceding it.
Once a function is defined, an array of values can be passed to it using the map()
function.
# Creating an array
xvals = [-3, -2.5, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3];
# Creating the function
function sqr(a)
return a^2
end
sqr (generic function with 1 method)
The map()
function will now map the function to each value in the array.
# Mapping the array to the function
map(sqr, xvals)
13-element Array{Float64,1}: 9.0 6.25 4.0 2.25 1.0 0.25 0.0 0.25 1.0 2.25 4.0 6.25 9.0
The dot notation after a function achieves the same results.
sqr.(xvals)
13-element Array{Float64,1}: 9.0 6.25 4.0 2.25 1.0 0.25 0.0 0.25 1.0 2.25 4.0 6.25 9.0
It is possible to limit a function to accepting only cenrtain argument types.
function m(x::Int)
return 3 * x
end
m (generic function with 1 method)
Using the methods()
function, we can now see that only integers argument values are allowed.
methods(m)
# Calling the function with an integer
m(3)
9
A flotaing point value such as m(3.)
will result in an error.
MethodError: no method matching m(::Float64)
Closest candidates are:
m(!Matched::Int64) at In[58]:2
Stacktrace:
[1] top-level scope at In[62]:1
Stabby lambda functions as they are called, are quick-and-dirty functions. They are examples of anonymous functions, the latter referring to the fact that they don't have a name. The do block is also a form of anonymous function. Let's look at some examples.
# The Julia syntax uses the -> character combinations, hence stabby!
x -> 2x^2 + 3x - 2
#5 (generic function with 1 method)
We can now us the map()
function to apply the values in an array to this stabby function. Note that the stabby function cannot be called.
map(x -> 2x^2 + 3x - 2, [1, 2, 3, 4, 5])
5-element Array{Int64,1}: 3 12 25 42 63
There is another way of achieving this using do
blocks.
# Let's do something
map([1, 2, 3, 4, 5]) do x
2x^2 + 3x - 2
end
5-element Array{Int64,1}: 3 12 25 42 63
The do
block can do some more!
map([3, 6, 9, 10, 11]) do x
if mod(x, 3) == 0 # If the value is divisible by 3
100x
elseif mod(x, 3) == 1 # If the remainder after dividing by 3 is 1
200x
else
mod(x, 3) == 2 # If the remainder is 2
300x
end
end
5-element Array{Int64,1}: 300 600 900 2000 3300
As the title of this section implies, we can pass a function as an argument. That functional argument will actually call the function.
# First function
function string_func(s)
str = s()
print("I love $str", "!")
end
# Second function
function luv()
return("Julia! She's awesome")
end
luv (generic function with 1 method)
string_func(luv)
# Calling the function string_func
# Passing a function as an argument, which then calls that function
# The called luv function returns the string Julia, which is now the argument of the originally called function
I love Julia! She's awesome!