Conditional evaluations
This lecture handles control flow. The first part focuses on if conditions and the second one of loops.
if-elseif-else statement
In many cases, we have to decide what to do for different conditions. Julia supports the standard if-elseif-else syntax, which determines which part of the code will be evaluated. This depends on the logical expression value. For example, the following function compares two numerical values.
function compare(x, y)
if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
return
endIf the expression x < y is true, the function prints "x is less than y", otherwise, the expression x > y is evaluated, and if it is true, the function prints "x is greater than y". If neither expression is true, the function prints the remaining option "x is equal to y".
julia> compare(1, 2.3)
x is less than y
julia> compare(4.7, 2.3)
x is greater than y
julia> compare(2.3, 2.3)
x is equal to yThe elseif and else keywords are optional. Moreover, it is possible to use as many elseif blocks as needed.
julia> x, y = 2, 1;
julia> if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
end
x is greater than y
julia> if x < y
println("x is less than y")
endThe conditions in the if-elseif-else construction are evaluated until the first one is true. The associated block is then evaluated, and no other condition expressions or blocks are evaluated.
In contrast to languages like Python or Matlab, the logical expression in the if-elseif-else statement must always return a boolean value. Otherwise, an error will occur.
julia> if 1
println("Hello")
end
ERROR: TypeError: non-boolean (Int64) used in boolean context
[...]The if blocks do not introduce a local scope, i.e., it is possible to introduce a new variable inside the if block and use this variable outside the block.
julia> x, y = 2, 1;
julia> if x < y
z = y
else
z = x
end
2
julia> z
2However, it is necessary to ensure that the variable is always declared in all cases.
function compare(x, y)
if x < y
z = y
elseif x > y
z = x
end
return z
endThe function defined above works only for numbers which are not equal.
julia> compare(1, 2.3)
2.3
julia> compare(4.7, 2.3)
4.7
julia> compare(2.3, 2.3)
ERROR: UndefVarError: `z` not defined
[...]The if blocks always return a value equal to the last expression evaluated in the if block. In other words, it is possible to assign the value returned by the if block to a new variable.
function compare(x, y)
z = if x < y
y
else
x
end
return z
endIn the example above, the variable z equals y if the expressionx < y evaluates as true. Otherwise, the variable z equalsx.
julia> compare(1, 2.3)
2.3
julia> compare(4.7, 2.3)
4.7
julia> compare(2.3, 2.3)
2.3Write the fact(n) function that computes the factorial of n. Use the following function declaration:
function fact(n)
# some code
endMake sure that the input argument is a non-negative integer. For negative input arguments and for arguments that can not be represented as an integer, the function should throw an error.
Hint: use recursion, the isinteger function and the error function. The or operator is written as |.
Solution:
We split the solution into three cases:
- If
nis smaller than zero or not an integer, we throw an error. - If
nis equal to zero, the function returns1. - If
nis a positive integer, we use recursion.
function fact(n)
return if n < 0 | !isinteger(n)
error("argument must be non-negative integer")
elseif n == 0
1
else
n * fact(n - 1)
end
endSince the if block returns a value from the latest evaluated expression, it is possible to use it after the return keyword to define the function output. However, it is also possible to omit the return keyword since functions return the last evaluated expression if the return keyword is not used.
julia> fact(4)
24
julia> fact(0)
1
julia> fact(-5)
ERROR: argument must be non-negative integer
[...]
julia> fact(1.4)
ERROR: argument must be non-negative integer
[...]Ternary operator
The ternary operator ? is closely related to the if-else statement. It can instead be used to decide between two simple options. The syntax is the following:
a ? b : cwhich can be read as "if a is true, then evaluate b; otherwise, evaluate c". The white spaces around ? and : are mandatory.
julia> x, y = 2, 1;
julia> println(x < y ? "x is less than y" : "x is greater than or equal to y")
x is greater than or equal to yIn this case, there are two possibilities:
- if
x < yis true, then the string"x is less than y"is returned, - if
x < yis false, then the string"x is greater than or equal to y"is returned.
Since we wrapped the whole expression into the println function, the ternary operator output is printed.
Short-circuit evaluation
Julia provides the so-called short-circuit evaluation which is similar to the conditional evaluation. The behaviour exists in most imperative programming languages, which have the && and || boolean operators. In a series of boolean expressions connected by these operators, only the minimal number of expressions is evaluated to determine the final boolean value of the entire chain:
- In the expression
a && b, the subexpressionbis only evaluated ifaevaluates true. - In the expression
a || b, the subexpressionbis only evaluated ifaevaluates to false.
To investigate this behavior, let's define the following two functions:
t(x) = (println(x); true)
f(x) = (println(x); false)The t function prints x and returns true. Similarly, the f function prints x and returns false. Using these two functions, we can easily determine which expressions are evaluated when using short-circuit evaluation.
julia> t(1) && println(2) # both expressions are evaluated
1
2
julia> f(1) && println(2) # only the first expression is evaluated
1
false
julia> t(1) || println(2) # only the first expression is evaluated
1
true
julia> f(1) || println(2) # both expressions are evaluated
1
2Boolean operations without short-circuit evaluation can be done with the bitwise boolean operators & and | introduced in previous lecture. These are normal functions, which happen to support infix operator syntax, but always evaluate their arguments.
julia> f(1) & t(2)
1
2
false
julia> f(1) && t(2)
1
falseWhen multiple && and || are chained together, && has a higher precedence than ||. For example, a || b && c && d || e is equivalent to a || (b && c && d) || e.
julia> t(1) && t(2) || println(3) # the first two expressions are evaluated
1
2
true
julia> f(1) && t(2) || println(3) # the first and the last expressions are evaluated
1
3
julia> f(1) && f(2) || println(3) # the first and the last expressions are evaluated
1
3
julia> t(1) && f(2) || println(3) # all expressions are evaluated
1
2
3
julia> t(1) || t(2) && println(3) # the first expression is evaluated
1
true
julia> f(1) || t(2) && println(3) # all expressions are evaluated
1
2
3
julia> f(1) || f(2) && println(3) # the first two expressions are evaluated
1
2
false
julia> t(1) || f(2) && println(3) # the first expression is evaluated
1
trueRewrite the factorial function from the exercises above. Use the short-circuit evaluation to check if the given number is a non-negative integer and the ternary operator for recursion.
Solution:
Since we want to check if the input number is a non-negative integer, we need to check two conditions. It can be done separately by the short-circuit evaluation.
function fact(n)
isinteger(n) || error("argument must be non-negative integer")
n >= 0 || error("argument must be non-negative integer")
return n == 0 ? 1 : n * fact(n - 1)
endThis can be further simplified by combining the && and || operators.
function fact(n)
isinteger(n) && n >= 0 || error("argument must be non-negative integer")
return n == 0 ? 1 : n * fact(n - 1)
endSince && has higher precedence than ||, the error function is evaluated only if isinteger(n) && n >= 0 is violated. We can then check that this function works the same as the fact function from above.
julia> fact(4)
24
julia> fact(0)
1
julia> fact(-5)
ERROR: argument must be non-negative integer
[...]
julia> fact(1.4)
ERROR: argument must be non-negative integer
[...]