Zoom Functions
Functions are objects containing a set of instructions.
When you pass a tuple of argument(s) (possibly an empty tuple) to them, you get one or more values as output.
Operators
Operators are functions and can be written in a way that shows the tuple of arguments more explicitly:
3 + 2 == +(3, 2) # `==` tests for equality
true
+(3, 2)
5
Function definition
There are 2 ways to define a new function:
Long form
function <name>(<arguments>)
<body>
end
function hello1()
println("Hello")
end
hello1 (generic function with 1 method)
Assignment form
<name>(<arguments>) = <body>
hello1() = println("Hello")
hello1 (generic function with 1 method)
The function hello1
defined with this compact syntax is exactly the same as the one we defined above.
Stylistic convention
Julia suggests to use lower case, without underscores, as function names.
Calling functions
Since you pass a tuple to a function when you run it, you call a function by appending parentheses to its name:
hello1()
Hello
Arguments
No argument
Our function hello
does not accept any argument. If we pass an argument, we get an error message:
hello1("Bob")
LoadError: MethodError: no method matching hello1(::String)
[0mClosest candidates are:
[0m hello1() at In[5]:1
One argument
To define a function which accepts an argument, we need to add a placeholder for it in the function definition.
function hello2(name)
println("Hello name")
end
hello2 (generic function with 1 method)
hello2("Bob")
Hello name
Mmm … not quite … this function works but does not give the result we wanted.
Here, we need to use string interpolation:
function hello3(name)
println("Hello $name")
end
hello3 (generic function with 1 method)
$name
in the body of the function points to name
in the tuple of argument.
When we run the function, $name
is replaced by the value we used in lieu of name
in the function definition:
hello3("Bob")
Hello Bob
function cube(a)
a ^ 3
end
cube(4)
64
Multiple arguments
Now, let’s write a function which accepts 2 arguments. For this, we put 2 placeholders in the tuple passed to the function in the function definition:
function hello4(name1, name2)
println("Hello $name1 and $name2")
end
hello4 (generic function with 1 method)
This means that this function expects a tuple of 2 values:
hello4("Bob", "Pete")
Hello Bob and Pete
Default arguments
You can set a default value for some or all arguments. In this case, the function will run with or without a value passed for those arguments. If no value is given, the default is used. If a value is given, it will replace the default.
function hello5(name="")
println("Hello $name")
end
hello5 (generic function with 2 methods)
hello5()
Hello
hello5("Bob")
Hello Bob
Returning the result
In Julia, functions return the value(s) of the last expression automatically.
If you want to return something else instead, you need to use the return
statement. This causes the function to exit early.
function test1(x, y)
x + y
end
function test2(x, y)
return x + y
end
function test3(x, y)
x * y
x + y
end
function test4(x, y)
return x * y
x + y
end
function test5(x, y)
return x * y
return x + y
end
function test6(x, y)
x * y, x + y
end
test1(1, 2)
test2(1, 2)
test3(1, 2)
test4(1, 2)
test5(1, 2)
test6(1, 2)
Anonymous functions
Anonymous functions are functions which aren’t given a name:
function (<arguments>)
<body>
end
In compact form:
<arguments> -> <body>
function (name)
println("Hello $name")
end
#1 (generic function with 1 method)
name -> println("Hello $name")
#3 (generic function with 1 method)
When would you want to use anonymous functions?
This is very useful for functional programming (when you apply a function—for instance map
—to other functions to apply them in a vectorized manner which avoids repetitions).
map(name -> println("Hello $name"), ["Bob", "Lucie", "Sophie"]);
Hello Bob
Hello Lucie
Hello Sophie
Pipes
|>
is the pipe in Julia.
It redirects the output of the expression on the left as the input of the expression on the right.
println("Hello")
"Hello" |> println
sqrt(2) == 2 |> sqrt
true
Function composition
You can pass a function inside another function:
<function2>(<function1>(<arguments>))
<arguments>
will be passed to <function1>
and the result will then be passed to <function2>
.
An equivalent syntax is to use the composition operator โ
(in the REPL, type \circ
then press tab):
(<function2> โ <function1>)(<arguments>)
# sum is our first function
sum(1:3)
6
# sqrt is the second function
sqrt(sum(1:3))
2.449489742783178
# This is equivalent
(sqrt โ sum)(1:3)
2.449489742783178
Mutating functions
Functions usually do not modify their argument(s):
a = [-2, 3, -5]
3-element Vector{Int64}:
-2
3
-5
sort(a)
3-element Vector{Int64}:
-5
-2
3
a
3-element Vector{Int64}:
-2
3
-5
Julia has a set of functions which modify their argument(s). By convention, their names end with !
sort!(a);
a
3-element Vector{Int64}:
-5
-2
3
Broadcasting
To apply a function to each element of a collection rather than to the collection as a whole, Julia uses broadcasting.
a = (2, 3)
(2, 3)
a
to the string function, that function applies to the whole collection:
string(a)
"(2, 3)"
broadcast(string, a)
("2", "3")
string.(a)
("2", "3")
We will see more examples in the array section.