Julia Control Flow
Control flow statements are implemented by setting one or more condition statements in a program. When the condition is true, the specified program code is executed; when the condition is false, other specified code is executed.
Julia provides a large number of control flow statements:
Compound expressions:beginand;。
Conditional expressions:if-elseif-elseand?:(ternary operator).
Short-circuit evaluation:logical operators&&(and) and||(or), as well as chained comparisons.
Loop statements:loops:whileandfor。
Exception handling:try-catch、errorandthrow。
-
Task (coroutine):yieldto。
Compound Expressions
begin ... endAn expression can sequentially evaluate several subexpressions and return the value of the last subexpression:
Example
x = 1
y = 2
x + y
end
3
Because these are very short expressions, they can simply be placed on one line, which is the origin of the;chain:
Example
3
In actual use, it is not required that the begin block be multi-line, or that the ; chain be single-line:
Example
3
julia> (x = 1;
y = 2;
x + y)
3
Conditional Expressions
A conditional expression can decide which block of code to execute based on the value of a Boolean expression.
if-elseif-else syntax:
/*When the Boolean expression1is true, execute*/
elseif boolean_expression 2
/*When the Boolean expression2is true, execute*/
elseif boolean_expression 3
/*When the Boolean expression3is true, execute*/
else
/*When none of the above conditions are true, execute*/
Anifstatement can be followed by an optionalelse if...elsestatement, which can be used to test multiple conditions.
When using if...else if...else statements, the following points need to be noted:
- An if can be followed by zero or one else, and the else must come after all else ifs.
- An if can be followed by zero or more else ifs, and the else ifs must come before the else.
- Once an else if matches successfully, the other else ifs or else will not be tested.
Below is an analysis of the if-elseif-else conditional syntax:
Example
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
If the expression x < y is true, the corresponding code block is executed; otherwise, the conditional expression x > y is evaluated. If it is true, the corresponding code block is executed; if no expression is true, the else code block is executed.
Example
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
end
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
Ternary Operator
The ternary operator?:is similar to if-elseif-else syntax:
a ? b : cIn?The previous expression a is a conditional expression. If condition a is true, the ternary operator evaluates expression b before the :; if condition a is false, it executes expression c after the :.
Note:?and:Spaces around are mandatory. An expression like a?b:c is not a valid ternary expression (but line breaks after ? and : are allowed).
Example
julia> println(x < y ? "less than" : "not less than")
less than
julia> x = 1; y = 0;
julia> println(x < y ? "less than" : "not less than")
not less than
If the expression x < y is true, the entire ternary operator executes the string "less than"; otherwise, it executes the string "not less than".
Chaining and nesting ternary operators:
Example
x > y ? "x is greater than y" : "x is equal to y")
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
To facilitate chained value passing, operators are connected from right to left.
Similar to if-elseif-else, the expressions before and after:are executed accordingly only when the conditional expression is true or false:
Example
v (generic function with 1 method)
julia> 1 < 2 ? v("yes") : v("no")
yes
"yes"
julia> 1 > 2 ? v("yes") : v("no")
no
"no"
Short-Circuit Evaluation
In Julia, the&&and||operators correspond to logical"and"and"or"operations.
- In the expression
a && b, the subexpressionbis executed only whenaistrue; if a is false, false is returned directly. - In the expression
a || b, the subexpressionbis executed only whenaisfalse; if a is true, a is returned directly.
&& Example
┌ Warning: An odd Number!
└ @ Main REPL[5]:1
julia> isodd(4) && @warn("An odd Number!")
false
|| Example
true
julia> isodd(4) || @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[8]:1
Both && and || depend on the right side, but && has a higher precedence than ||. See the example:
Example
t (generic function with 1 method)
julia> f(x) = (println(x); false)
f (generic function with 1 method)
julia> t(1) && t(2)
1
2
true
julia> t(1) && f(2)
1
2
false
julia> f(1) && t(2)
1
false
julia> f(1) && f(2)
1
false
julia> t(1) || t(2)
1
true
julia> t(1) || f(2)
1
true
julia> f(1) || t(2)
1
2
true
julia> f(1) || f(2)
1
2
false
Loop Statements
Loop statements usewhileandfortwo keywords to implement.
Below is an example of awhileloop:
Example
julia> while i <= 5
println(i)
global i += 1
end
1
2
3
4
5
The while loop evaluates the conditional expression (i <= 5). As long as it is true, it keeps executing the body of the while loop. When the conditional expression is false, that is, when i = 6, the loop ends.
The for loop is more convenient to use. The above example implemented with a for loop is as follows:
Example
println(i)
end
1
2
3
4
5
Here,1:5is a range object representing the sequence of numbers 1, 2, 3, 4, 5.
The for loop iterates over these values, assigning each variable i.
A very important difference between the for loop and the previous while loop is scope, that is, the visibility of variables. If variable i has not been introduced in another scope, inside the for loop it is only visible within the for loop, and is not visible outside or after it. You need a new interactive session instance or a new variable name to test this feature:
Example
println(j)
end
1
2
3
4
5
julia> j
ERROR: UndefVarError: j not defined
In general, for loop components can be used to iterate over any container. In this case, compared to =, the other (but completely identical) keywordinor∈is more commonly used because it makes the code clearer:
Example
println(i)
end
1
4
0
julia> for s ∈ ["foo","bar","baz"]
println(s)
end
foo
bar
baz
For convenience, we may terminate a while loop before the test condition becomes false, or stop a for loop before reaching the end of the iterated object. This can be done with the keywordbreak:
Example
julia> while true
println(i)
if i >= 5
break
end
global i += 1
end
1
2
3
4
5
julia> for j = 1:1000
println(j)
if j >= 5
break
end
end
1
2
3
4
5
Without the break keyword, the while loop above would never end by itself, and the for loop would iterate up to 1000. These loops can all use break to terminate early.
In some scenarios, it is necessary to end the current iteration directly and immediately move to the next iteration. Thecontinuekeyword can be used to accomplish this:
Example
if i % 3 != 0
continue
end
println(i)
end
3
6
9
This is a somewhat contrived example, because we could achieve the same functionality more concisely by negating the condition and placing the println call inside an if block. In practical applications, there is more code to run after continue, and there may be multiple places where continue is called.
Multiple nested for loops can be merged into one outer loop, which can be used to create the Cartesian product of their iterated objects:
Example
println((i, j))
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)
With this syntax, iteration variables can still be used normally with loop variables for indexing. For example,for i = 1:n, j = 1:iis legal, but using a break statement inside one loop will break out of the entire nested loop, not just the inner loop. Each time the inner loop runs, the variables (i and j) are assigned their current iteration values. So assignments to i are not visible to subsequent iterations:
Example
println((i, j))
i = 0
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)
If this example is rewritten using a for keyword for each variable, the output will be different: the second and fourth variables contain 0.
You can use zip to iterate over multiple containers simultaneously in a single for loop:
Example
println((j,k))
end
(1, 4)
(2, 5)
(3, 6)
julia> for x in zip(0:15, 100:110, 200:210)
println(x)
end
(0, 100, 200)
(1, 101, 201)
(2, 102, 202)
(3, 103, 203)
(4, 104, 204)
(5, 105, 205)
(6, 106, 206)
(7, 107, 207)
(8, 108, 208)
(9, 109, 209)
(10, 110, 210)
// Handle inconsistent numbers of elements
julia> for x in zip(0:10, 100:115, 200:210)
println(x)
end
(0, 100, 200)
(1, 101, 201)
(2, 102, 202)
(3, 103, 203)
(4, 104, 204)
(5, 105, 205)
(6, 106, 206)
(7, 107, 207)
(8, 108, 208)
(9, 109, 209)
(10, 110, 210)
Using zip creates an iterator that is a tuple of sub-iterators for the containers passed to it. The zip iterator iterates over all sub-iterators in order, selecting the ii-th element of each sub-iterator on the ii-th iteration of the for loop. As soon as any sub-iterator is exhausted, the for loop stops.
for statements can nest multiple loop conditions, using commas,to separate:
Example
@show (n, m)
end
(n, m) = (1, 1)
(n, m) = (1, 2)
(n, m) = (1, 3)
(n, m) = (1, 4)
(n, m) = (1, 5)
(n, m) = (2, 1)
(n, m) = (2, 2)
(n, m) = (2, 3)
(n, m) = (2, 4)
(n, m) = (2, 5)
(n, m) = (3, 1)
(n, m) = (3, 2)
(n, m) = (3, 3)
(n, m) = (3, 4)
(n, m) = (3, 5)
(n, m) = (4, 1)
(n, m) = (4, 2)
(n, m) = (4, 3)
(n, m) = (4, 4)
(n, m) = (4, 5)
(n, m) = (5, 1)
(n, m) = (5, 2)
(n, m) = (5, 3)
(n, m) = (5, 4)
(n, m) = (5, 5)
Comprehensions
A comprehension is a distinctive data processing method that can construct a new data sequence structure from one data sequence.The format is as follows:
[out_exp_res for out_exp in input_list]
or
[expressionforvariableinlistifcondition]
[out_exp_res for out_exp in input_list if condition]
Example
5-element Array{Int64,1}:
1
4
9
16
25
We can also specify the type of elements we want to generate:
Example
5-element Array{Complex,1}:
1 + 0im
4 + 0im
9 + 0im
16 + 0im
25 + 0im
Traversing Arrays
Sometimes we want to iterate over each element of an array, including the index number of the element.
Julia provides theenumerate(iter)function; the parameteriteris an iterable object. This function will generate the index numbers as well as the value corresponding to each index.
Example
julia> for (index, value) in enumerate(a)
println("$index $value")
end
1 a
2 b
3 c
julia> arr = rand(0:9, 4, 4)
4×4 Array{Int64,2}:
7 6 5 8
8 6 9 4
6 3 0 7
2 3 2 4
julia> [x for x in enumerate(arr)]
4×4 Array{Tuple{Int64,Int64},2}:
(1, 7) (5, 6) (9, 5) (13, 8)
(2, 8) (6, 6) (10, 9) (14, 4)
(3, 6) (7, 3) (11, 0) (15, 7)
(4, 2) (8, 3) (12, 2) (16, 4)
Exception Handling
During program execution, if an unexpected condition occurs, a function may be unable to return a reasonable value to its caller. In such cases, it is best to let the unexpected condition terminate the program and print debug error information, which makes it easier for developers to handle their code.
Through thetry / catchstatement, exceptions can be handled conveniently.
For example, in the code below, the square root function sqrt raises an exception. Through try / catch, we can accurately output the exception information.
Example
sqrt("ten")
catch e
println("You need to enter a number")
end
You need to enter a number
finally Clause
When programming with state changes or using resources such as files, it is often necessary to perform cleanup work (such as closing files) when the code ends. Because exceptions can cause some code blocks to exit before normal completion, this can complicate the above work. The finally keyword provides a way to run a certain piece of code when a code block exits, regardless of how it exits.
Here is an example that ensures an opened file is closed:
Example
try
# operate on file f
finally
close(f)
end
When control flow leaves the try block (for example, when return is encountered, or when it ends normally), close(f) will be executed. If the try block exits due to an exception, the exception continues to propagate. The catch block can be used together with try and finally. In this case, the finally block runs after catch handles the error.
throw Function
We can use throw to explicitly create exceptions.
For example, if a function is only defined for non-negative numbers, you can use throw to raise a DomainError when the input parameter is negative.
Example
f (generic function with 1 method)
julia> f(1)
0.36787944117144233
julia> f(-1)
ERROR: DomainError with -1:
argument must be nonnegative
Stacktrace:
[1] f(::Int64) at ./none:1
Note that DomainError without parentheses is not an exception, but an exception type. We need to call it to obtain an Exception object:
Example
true
julia> typeof(DomainError) <: Exception
false
In addition, some exception types accept one or more arguments for error reporting:
Example
ERROR: UndefVarError: x not defined
We can easily implement this mechanism with a custom exception type, following the pattern of UndefVarError:
Example
var::Symbol
end
julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined")
Task (Coroutine)
Task (coroutine) provides non-local flow control, which makes it possible to switch between temporarily suspended computational tasks. We will cover this in detail in the later chapter on asynchronous programming.
Other Extensions