Functions in Julia

Adarsh Ms
The Startup
Published in
6 min readSep 4, 2020

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)
end
position_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)
end
name_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
end
addition(1,2) # Yields value 3

This is equivalent to:

function addition(a, b)
a + b
end
addition(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
end
arithmetic(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)
end
function 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)
end
function 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.

--

--

Adarsh Ms
The Startup

A passionate engineer who thrives on using programming languages to converse with machines.