Functions in Julia
Single, multiple, compound expressions, anonymous functions and scopes
This is part 8 of the Julia Tutorials (a series of tutorials on Julia). If you are not familiar with Julia Control flows, please go through part 7 for better understanding.
If you are not familiar with Julia and want to get started with it, feel free to check out the complete series @ —
Note: Use Julia’s REPL to quickly follow along with the codes in this tutorial
Functions are the important building blocks in Julia, a function is simply a block of code used to perform a single/set of tasks. In Julia, functions can be defined in 2 different ways.
Single Expression Functions
These functions are used to define simple tasks/actions.
Syntax:
functionName(<arguments>) = # code to execute
Example:
Let’s define a function that receives 2 arguments m
and n
and returns their product.
# Function definition
product(m, n) = m * n# Function call
product(5, 2) # Yields value 10
Multiple Expression Functions
These functions are used when blocks of codes are required to be included in the function’s body.
Syntax:
function functionName(<arguments>)
# Blocks of code
....
end
Example:
Let’s create a function to display the results of multiplication and division performed on 2 variables m
and n
# Function definition
function arithmetic(m, n)
product = m * n
quotient = m / n
println("Product of $m*$n is: $product")
println("Quotient of $m/$n is: $quotient")
end# Function call
arithmetic(10, 2)
Now, before going into next sections, let’s discuss more about arguments and returns.
Arguments
Arguments are independent items, or variables, that contain data or codes. In Julia, arguments can be specified in 2 ways, positional and named.
Positional arguments — These are the commonly used type of argument. Positional arguments are specified by their position and can’t called using their name, doing so will result in an error.
Example:
function position_demo(a, b=10)
print(a, b)
endposition_demo(1)
position_demo(1, 5)
position_demo(1, b=4) # Results in an error
In the above example b
is an optional argument, i.e, if not specified in the function call, it will take the default value assigned (in this case, it is 10
).
Keyword arguments — These type of arguments can be called only by their names. They are defined after a ;
Example:
function name_demo(a, b=10; c, d=5)
print(a, b, c, d)
endname_demo(1,2,c=3) # a -> 1, b -> 2, c -> 3
name_demo(1,2,3) # Results in an error
name_demo(1, c=5, d=2) # a -> 1, b -> 10, c -> 5, d -> 2
From the above examples, you can see that positional arguments can’t be called by name and keyword arguments can’t be called by position.
Type assertion in arguments
We can also specify desired types for function arguments if we want our function to work only on some specific types.
Example:
function name_demo(a::Float64, b::Float64)
print(a, b)
end
Returns
Return statements are used to terminate the flow of current function and return the flow/value to the calling function. In Julia, return
is optional since the last computed value of the function is returned by default.
Example:
function addition(a, b)
return a + b
endaddition(1,2) # Yields value 3
This is equivalent to:
function addition(a, b)
a + b
endaddition(1,2) # Yields value 3
As you can see from the above examples, both functions return the sum of a & b. Though Julia returns the last computed value in a function by default, it is still a best practice to specify return
statement in your functions.
You can also return multiple values.
Example:
function arithmetic(a, b)
return a + b, a - b
endarithmetic(1,2) # Yields tuple (3, -1)
Compound Expressions
Compound expressions are used to execute a sequence of functions one by one and return the last computed value. Compound expressions can be created in 2 ways
Using begin/end
Example:
square_root = begin
a = 5
a ^= 2
end
Using ;
Example:
square_root = (a=5; a ^= 2)
Anonymous Functions
Anonymous functions are functions that are not bound to an identifier. They do not have a defined name and in some programming languages, they are defined using lambda
keyword. In Julia, anonymous functions can be created in 2 ways:
Using Stabby lambda ->
Example:
map(x -> 2x^3 + x^2 - 2x + 4, [2, 3, 4, 5])
map
is a higher-order function that applies a given function to each element of an iterable, e.g. a list, returning a list of results in the same order.
In the above example, the expression x -> 2x^3 + x^2 — 2x + 4
is a stabby lambda function.
Note: Use of stabby lambda functions are discouraged by the Julia style guidelines since it can make code confusing and hard to unit test.
Using do
blocks
Example:
map(["start", "play", "stop", "resume"]) do x if x == "start"
"Starting the movie..."
elseif x == "play"
"Playing the movie..."
elseif x == "resume"
"Resuming the movie..."
elseif x == "stop"
"Stopping the movie..."
else
"Sorry, I'm not allowed to do this!"
end
end
Scopes
In part 7 of this tutorial series (Julia Control flows), we came across variable scopes in loops. If you are familiar with other programming languages, you would probably know about scopes. Scopes are simply the extent up to which an entity is accessible in a program, i.e, for entities such as variables, the scope refers to the region/block in a program where the variable can be referenced by it’s name.
Julia implements lexical scoping, i.e, scope of an entity is not inherited from it’s caller, but it’s definition. Let’s look at an example to understand what this means
function foo()
println(x)
end
function bar()
x = 20
foo()
end
In the above example, 2 functions namely foo
and bar
are defined. The function foo
is called upon by the function bar
. What do you think will happen, does the value of x
gets printed?
As you can see, calling the function, bar
will results in an UndefVarError
because the variable x
is only visible inside bar
. In other words, the assignment of x
is outside the scope of the function foo
.
If we want to create a variable that can be accessed by any function in the program, we can use global scope.
Global Scope — Variables defined in global scopes can be accessed by all the functions in the program.
Example:
x = 10function foo()
println(x)
endfunction bar()
x = 20
foo()
end
Since we declared variable x
globally, this won’t result in an error. But what value will get printed upon calling bar
? Will it be 20
or 10
?
The value 10
gets printed because, the re assignment of the variable x
inside bar
was not visible to foo
. If we want this re assignment to be reflected in foo
as well, then it needs to be done globally,
x = 10function foo()
println(x)
endfunction bar()
global x
x = 20
foo()
end
In the above example, the re assignment was done globally, so it was visible to all other functions in the program. Whenever you want to update a variable globally inside a function or loop, first access the variable using the global
keyword and then do the updates.
Note: Though global scopes are helpful, they incur performance penalty. So be sure to use global scope only when necessary.
Next Up — Methods in Julia