Easy Tutorial
❮ Julia Date Time Julia Data Type ❯

Julia Control Flow

Control flow statements are implemented by setting one or more conditional statements in the program. They execute specified program code when the condition is true and other specified code when the condition is false.

Julia provides a variety of control flow statements:

-

Compound Expressions: begin and ;.

-

Conditional Expressions: if-elseif-else and ?: (ternary operator).

-

Short-Circuit Evaluation: Logical operators && (and) and || (or), and chained comparisons.

-

Loop Statements: Loops: while and for.

-

Exception Handling: try-catch, error, and throw.

-

Task (Coroutine): yieldto.


Compound Expressions

The begin ... end expression evaluates several sub-expressions in sequence and returns the value of the last sub-expression:

Example

julia> z = begin
           x = 1
           y = 2
           x + y
       end
3

Since these are very short expressions, they can be simply placed on one line, which is the origin of the ; chain:

Example

julia> z = (x = 1; y = 2; x + y)
3

In practice, the begin block does not have to be multi-line, nor does the ; chain have to be single-line:

Example

julia> begin x = 1; y = 2; x + y end
3

julia> (x = 1;
       y = 2;
       x + y)
3

Conditional Expressions

Conditional expressions decide which block of code to execute based on the value of a boolean expression.

The if-elseif-else syntax:

if boolean_expression 1
   # Executed when boolean_expression 1 is true
elseif boolean_expression 2 
   # Executed when boolean_expression 2 is true
elseif boolean_expression 3 
   # Executed when boolean_expression 3 is true
else 
   # Executed when none of the above conditions are true
end

An if statement can be followed by an optional else if...else statement, which is useful for testing multiple conditions.

When using if...else if...else statements, note the following:

Here is an analysis of the if-elseif-else conditional syntax:

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

If the expression x < y is true, then the corresponding code block is executed; otherwise, the condition expression x > y is checked, and if it is true, the corresponding code block is executed; if no expression is true, the else code block is executed.

Example

julia> function test(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
       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 the if-elseif-else syntax:

The expression a before ? is a conditional expression; if condition a is true, the ternary operator evaluates the expression b before :; if condition a is false, it evaluates the expression c after :.

Note: Spaces next to ? and : are mandatory, like a?b:c is not a valid ternary expression (but newlines after ? and : are allowed).

Example

julia> x = 1; y = 2;
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 will execute the string "less than"; otherwise, it will execute the string "not less than".

Chained nested use of the ternary operator:

Example

julia> test(x, y) = println(x < y ? "x is less than y" :
                             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, the operators are connected from right to left.

Similar to if-elseif-else, the expressions before and after : are executed only when the condition expression is true or false, respectively:

Example

julia> v(x) = (println(x); x)
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, respectively.

&& Example

julia> isodd(3) && @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[5]:1

julia> isodd(4) && @warn("An odd Number!")
false

|| Example

julia> isodd(3) || @warn("An odd Number!")
true

julia> isodd(4) || @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[8]:1

Both && and || rely on the right side, but && has higher precedence than ||. See the example:

Example

julia> t(x) = (println(x); true)
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 are implemented using the while and for keywords.

Here is an example of a while loop:

Example

julia> i = 1;

julia> while i <= 5
           println(i)
           global i += 1
       end
1
2
3
4
5

The while loop executes the condition expression (i <= 5), and as long as it is true, it continues to execute the body of the while loop. If the condition expression is false, which happens 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

julia> for i = 1:5
           println(i)
       end
1
2
3
4
5
Here, `1:5` is a range object representing the sequence of numbers 1, 2, 3, 4, 5.

The for loop iterates over these values, assigning each to the variable i.

A very important difference between the for loop and the previous while loop is scope, i.e., the visibility of variables. If the variable i is not introduced in another scope, it is only visible within the for loop and not outside or afterward. You need a new interactive session instance or a new variable name to test this feature:

## Example

```julia
julia> for j = 1:5
          println(j)
      end
1
2
3
4
5

julia> j
ERROR: UndefVarError: j not defined

Generally, the for loop component can be used to iterate over any container. In this case, the keyword in or is more commonly used instead of =, as it makes the code clearer:

Example

julia> for i in [1,4,0]
          println(i)
      end
1
4
0

julia> for s ∈ ["foo","bar","baz"]
          println(s)
      end
foo
bar
baz

For convenience, we might want to terminate a while loop before the test condition becomes false, or stop a for loop before it reaches the end of the iterable, which can be done with the break keyword:

Example

julia> i = 1;

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 above while loop would never end on its own, and the for loop would iterate up to 1000. These loops can be terminated early using break.

In some scenarios, you may need to end the current iteration and immediately proceed to the next one, which can be achieved with the continue keyword:

Example

julia> for i = 1:10
          if i % 3 != 0
              continue
          end
          println(i)
      end
3
6
9

This is a contrived example, as we could achieve the same functionality more concisely by negating the condition and placing the println call inside the if block. In practical applications, there might be more code to run after continue, and multiple places where continue is called.

Multiple nested for loops can be combined into a single outer loop, which can be used to create the Cartesian product of their iterables:

Example

julia> for i = 1:2, j = 3:4
          println((i, j))
      end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

With this syntax, the iteration variables can still use the loop variables for indexing, such as for i = 1:n, j = 1:i, which is valid. However, using the break statement within one loop will exit 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. Therefore, assignments to i are not visible to subsequent iterations:

Example

julia> for i = 1:2, j = 3:4
          println((i, j))
          i = 0
      end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

If this example were rewritten with a separate for keyword for each variable, the output would be different: the second and fourth outputs would contain 0.

Multiple containers can be iterated over simultaneously in a single for loop using zip:

Example

julia> for (j, k) in zip([1 2 3], [4 5 6 7])
          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)

// Handling inconsistent element counts 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 from the containers passed to it. The zip iterator will iterate through all sub-iterators in sequence, selecting the i-th element from each sub-iterator during the i-th iteration of the for loop. The for loop stops once any sub-iterator is exhausted.

The for statement can nest multiple loop conditions, separated by commas ,:

Example

julia> for n in 1:5, m in 1:5
                   @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

The format is as follows:

[expression for variable in list] 
[out_exp_res for out_exp in input_list]

or 

[expression for variable in list if condition]
[out_exp_res for out_exp in input_list if condition]

Example

julia> [X^2 for X in 1:5]
5-element Array{Int64,1}:
 1
 4
 9
 16
 25

We can also specify the type of elements we want to generate:

Example

julia> Complex[X^2 for X in 1:5]
5-element Array{Complex,1}:
 1 + 0im
 4 + 0im
 9 + 0im
 16 + 0im
 25 + 0im

Traversing Arrays

Sometimes we want to traverse each element of an array, including the index number of the element.

Julia provides the enumerate(iter) function, where iter is an iterable object, which generates both the index number and the corresponding value at each index.

Example

julia> a = ["a", "b", "c"];

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 not be able to return a reasonable value to the caller. In such cases, it is best to terminate the program and print debugging error messages to facilitate developers in handling their code.

The try / catch statement allows for convenient exception handling.

For example, in the following code, the sqrt function will throw an exception. Using try / catch, we can accurately output the exception information.

Example

julia> try
           sqrt("ten")

catch e println("You need to enter a number") end You need to enter a number

finally Clause

In programming where state changes occur or resources like files are used, it is often necessary to perform cleanup tasks (such as closing files) at the end of the code. Exceptions can complicate this because they may cause parts of the code block to exit before normal completion. The finally keyword provides a way to ensure that certain code runs upon exiting the block, regardless of how the block is exited.

Here is an example that ensures an opened file is closed:

f = open("file")
try
    # operate on file f
finally
    close(f)
end

When the control flow leaves the try block (e.g., due to a return or normal completion), close(f) will be executed. If the try block exits due to an exception, the exception will continue to be propagated. The catch block can be used in conjunction with try and finally. In this case, the finally block will run after the catch block handles the error.

throw Function

We can explicitly create exceptions using the throw function.

For example, if a function is only defined for non-negative numbers and a negative input is given, a DomainError can be thrown using throw.

julia> f(x) = x>=0 ? exp(-x) : throw(DomainError(x, "argument must be nonnegative"))
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 get an Exception object:

julia> typeof(DomainError(nothing)) <: Exception
true

julia> typeof(DomainError) <: Exception
false

Additionally, some exception types accept one or more arguments for error reporting:

julia> throw(UndefVarError(:x))
ERROR: UndefVarError: x not defined

We can easily implement this mechanism with custom exception types by following the pattern of UndefVarError:

julia> struct MyUndefVarError <: Exception
           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, allowing switching between suspended computational tasks. This will be further introduced in the asynchronous programming section.

❮ Julia Date Time Julia Data Type ❯