Julia Functions
A function is a group of statements that together perform a task.
In Julia, a function is an object that maps a tuple of argument values to a return value.
Functions in Julia are defined using the function
keyword, with the basic syntax being:
function functionname(args)
expression
expression
expression
...
expression
end
By default, the value returned by a function is the value of the last evaluated expression, so we do not see a return
statement above. However, if the return
keyword is used, the function will return immediately.
Example
julia> function f(x,y)
x + y
end
f (generic function with 1 method)
julia> f(2,3)
5
julia> function bills(money)
if money < 0
return false
else
return true
end
end
bills (generic function with 1 method)
julia> bills(50)
true
julia> bills(-50)
false
If a function needs to return multiple values, a tuple can be used:
Example
julia> function mul(x,y)
x+y, x*y
end
mul (generic function with 1 method)
julia> mul(5, 10)
(15, 50)
When a function contains only one expression, you can omit the function
keyword, setting the function name and parameters on the left side of the equals sign and the expression on the right side, similar to an assignment:
julia> f(x,y) = x + y
f (generic function with 1 method)
Without parentheses, the expression f
refers to the function object and can be passed around like any value:
julia> g = f;
julia> g(2,3)
5
Example
julia> f(a) = a * a
f (generic function with 1 method)
julia> f(5)
25
julia> func(x, y) = sqrt(x^2 + y^2)
func (generic function with 1 method)
julia> func(5, 4)
6.4031242374328485
Like variable names, Unicode characters can also be used as function names:
julia> ∑(x,y) = x + y
∑ (generic function with 1 method)
julia> ∑(2, 3)
5
Return Type
We can specify the return type of a function using the ::
operator.
Example
julia> function g(x, y)::Int8
return x * y
end;
julia> typeof(g(1, 2))
Int8
The above function example will ignore the types of x and y and return a value of type Int8.
Optional Parameters
In functions, we can set default values for parameters, so that if the parameter is not provided, the default value is used for computation:
The following example defines a function pos
with three parameters, where the parameter cz
has a default value of 0. This parameter can be omitted when calling the function:
Example
julia> function pos(ax, by, cz=0)
println("$ax, $by, $cz")
end
pos (generic function with 2 methods)
julia> pos(10, 30)
10, 30, 0
julia> pos(10, 30, 50)
10, 30, 50
Keyword Arguments
Sometimes, functions we define require a large number of parameters, which can be cumbersome to call because we might forget the order of the parameters. For example:
function foo(a, b, c, d, e, f)
...
end
We might forget the order of the parameters, leading to situations like:
foo("25", -5.6987, "hello", 56, good, 'ABC')
or
foo("hello", 56, "25", -5.6987, 'ABC', good)
This can be very confusing.
Julia's keyword arguments allow parameters to be identified by name rather than just by position, making these complex functions easier to use and extend.
To mark parameters with keywords, a semicolon ;
must be used after the unmarked parameters in the function, followed by one or more key-value pairs key=value
, as shown below:
Example
function foo(a; b=1, c=2)
...
end
julia> function foo(a, b; c = 10, d = "hi")
println("a is $a")
println("b is $b")
return "c => $c, d => $d"
end
foo (generic function with 1 method)
julia> foo(100, 20)
a is 100
b is 20
"c => 10, d => hi"
julia> foo("Hello", "tutorialpro", c=pi, d=22//7)
a is Hello
b is tutorialpro
"c => π, d => 22//7"
With keyword arguments, the position of the arguments becomes less important. We can call the function like this:
Example
julia> foo(c=pi, d=22/7, "Hello", "tutorialpro")
a is Hello
b is tutorialpro
"c => π, d => 3.142857142857143"
Anonymous Functions
An anonymous function is a function without a name.
Anonymous functions are declared dynamically during program execution and are identical to standard functions except for the absence of a name.
In Julia, anonymous functions can be used in many places, such as with map()
and list comprehensions.
Using anonymous functions makes our code more concise.
The syntax for anonymous functions uses the ->
symbol.
Example
julia> x -> x^2 + 2x - 1
#1 (generic function with 1 method)
julia> function (x)
x^2 + 2x - 1
end
#3 (generic function with 1 method)
The above example creates a function that takes a single argument x and returns the polynomial x^2 + 2x - 1
.
The primary use of anonymous functions is to pass them to functions that take other functions as arguments. A classic example is map()
, which applies a function to each element of an array and returns a new array with the resulting values:
Example
julia> map(round, [1.2, 3.5, 1.7])
3-element Vector{Float64}:
1.0
4.0
2.0
If the transformation function passed as the first argument to map()
already exists, using its name directly is fine. However, if the function to be used is not yet defined, using an anonymous function is more convenient:
Example
julia> map(x -> x^2 + 2x - 1, [1, 3, -1])
3-element Vector{Int64}:
2
14
-2
Anonymous functions that take multiple arguments can be written as (x, y, z) -> 2x + y - z
, and those without arguments as () -> 3
. This syntax for no-argument functions may seem odd, but it is necessary for delayed evaluation. This usage wraps a block of code into a no-argument function, which can then be called as f
.
For example, consider a call to get
:
Example
get(dict, key) do
# default value calculated here
time()
end
The above code is equivalent to calling get
with an anonymous function that contains the code between do
and end
, as shown below:
get(() -> time(), dict, key)
Here, the call to time
is delayed by wrapping it in a no-argument anonymous function. This anonymous function is called only when the requested key is missing from dict
.
Function Nesting and Recursion
In Julia, functions can be nested.
The following example nests an add1
function within an add
function:
Example
julia> function add(x)
Y = x * 2
function add1(Y)
Y += 1
end
add1(Y)
end
add (generic function with 1 method)
julia> d = 10
10
julia> add(d)
21
Similarly, functions in Julia can be recursive.
Recursion refers to a method where a function calls itself in its definition.
>
For example:
We use the ternary operator to test recursion, which operates on three operands expr ? a : b
. If expr
is true, the result is the evaluation of a
; otherwise, it is the evaluation of b
.
Example
julia> sum(x) = x > 1 ? sum(x-1) + x : x
sum (generic function with 1 method)
julia> sum(10)
55
The examples above are used to calculate the sum of all numbers up to and including a certain integer. In this recursion, there is a base case where the value is returned when x is 1.
The most famous example of recursion is calculating the nth Fibonacci number, which refers to the sequence 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368........
Starting from the 3rd term, each term in this sequence is equal to the sum of the two preceding terms.
Example
julia> fib(x) = x < 2 ? x : fib(x-1) + fib(x-2)
fib (generic function with 1 method)
julia> fib(10)
55
julia> fib(20)
6765
Map
The definition format for Map is as follows:
map(func, coll)
Here, func is a function that is applied to each element of the collection coll. Map typically includes anonymous functions and returns a new collection.
Example
julia> map(A -> A^3 + 3A - 3, [10,3,-2])
3-element Array{Int64,1}:
1027
33
-17
Filter
The definition format for Filter is as follows:
filter(function, collection)
The filter function returns a copy of the collection, removing elements that result in false when the function is called.
Example
julia> array = Int[1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> filter(x -> x % 2 == 0, array)
1-element Array{Int64,1}:
2