Control flow

Boolean types

Booleans are special data types that can have two values, true or false. They are essential for conducting control flow.

Boolean operators (copied from here)

Expression

Name

!x

negation

x && y

short-circuiting and

x || y

short-circuiting or

Numeric comparisons (copied partially from here)

Operator

Name

==

equality

!=,

inequality

<

less than

<=,

less than or equal to

>

greater than

>=,

greater than or equal to

, isapprox

approximate equality with relative tolerance error atol

===

programmatically identical equality comparison

Most operators work exactly the same as in other languages.

2 == 2
true
4>5
false

When I was learning coding in Julia, two operators were new to me: and ===.

Operator compares two different objects given the tolerance error atol:

(2000, 2000.1, atol=.5)
true
(2000, 2000.1; atol=.0001)
false

Operator === checks whether two objects are programmatically identical. In case of arrays or dictionaries, it means that we check whether two objects refer to the same memory slots. Illustrative example below:

x = [1,2]
y = x
z = deepcopy(x)

println("x==y: $(x==y)") #both objects have the same values
println("x==z: $(x==z)") #both objects have the same values
println("x===y: $(x===y)") #both objects refer to the same memory
println("x===z: $(x===z)") #both objects refer to different memory slots (even though they may have identical values)
x==y: true
x==z: true
x===y: true
x===z: false

The mentioned operators can be broadcasted. As a result we get arrays:

(1:1:3) .== (3:-1:1) #comparing [1 2 3] with [3 2 1]. It's true only for 2nd elements
3-element BitVector:
 0
 1
 0
.![true, false] #notice the order of `.` and `!`
2-element BitVector:
 0
 1

Conditional evaluations

Implementation of if/elseif/else is very standard and similar to other languages:

  if expr1 
      sth1
  elseif expr2
      sth2
  else 
      sth3
  end
  • if - do sth1 if expr1 is true

  • elseif - do sth2 if expr2 is true and expr1 is false

  • else - do sth3 if both expr1 and expr2 are false

Example below:

if 2!=2
    println("asd")
elseif 3>4
    println("3>4")
elseif 5==10
    println("5==10")
else
    println("nothing")
end
nothing

?: ternary operator

There is a ternary operator ?: for short conditional evaluations. Its syntax is cond ? sth1 : sth2. The result of this operation conducted on scalar is: do sth1 if cond is true and sth2 otherwise.

x = 4
even_x = x%2==0 ? "x is even" : "x is odd"
"x is even"

Example 6

Suppose we want to create the CRRA utlity function in Julia. It is defined as follows:

\[\begin{split} u(c_t, \eta) = \begin{cases}\frac{c_t^{1-\eta}-1}{1-\eta}, \text{if $\eta \neq 1$}\\ \ln (c_t), \text{if $\eta=1.$} \end{cases}\end{split}\]

We can use ? to define this function:

u(cₜ, η) = η==1 ? log(cₜ) : (cₜ^(1. -η)-1.)/(1. -η)
u (generic function with 1 method)
u(3,2)
0.6666666666666667

?: can be nested, just like in an example below:

x = 4

x<5 ?  ( 
        x%2==0 ? println("x is smaller than 5 and is even") : #x<5 is true and x%2==0 is true
                 println("x is smaller than 5 and is odd")    #x<5 is true and x%2==0 is false
        ) :
         println("x is greater or equal to 5")                #x<5 is false and x%2==0 is not evaluated
x is smaller than 5 and is even

We know that the CRRA function is well defined if \(\eta\geq 0.\) We can check this condition using nested ?:

u(cₜ, η) = η≥0 ? 
            (η==1 ? log(cₜ) : (cₜ^(1. -η)-1. )/(1. -η)) :
            @error "η= is not allowed!"
u (generic function with 1 method)
u(3, 2)
0.6666666666666667
u(3, -1)
┌ Error: η=-1 is not allowed!
└ @ Main In[13]:3

Elementwise ?:

Note

Suppose that we want to perform a following mapping for \(x\in 1,2, \ldots, 10:\)

\[\begin{split} f(x) = \begin{cases}x^2+1, \text{if $x$ is odd,}\\ -x^2, \text{if $x$ is even.}\\ \end{cases}\end{split}\]

Such a task can be done relatively easily in other languages. For instance in R we get it by using ifelse:

    result <- ifelse(x%%2==0, -x^2, x^2 + 1) #this is `R` code. It will not work in `Julia`

We might be tempted to make elementwise comparisons using .?.

x = collect( 1:10 );
result = ((x) .%2 .==0) .? -(x).^2 .: (x).^2.+1 
syntax: space before "." not allowed in "((x .% 2) .== 0) ." at In[16]:2

Stacktrace:
 [1] top-level scope
   @ In[16]:2
 [2] eval
   @ ./boot.jl:360 [inlined]
 [3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
   @ Base ./loading.jl:1094

As we can see, it will not work the way we would like. A solution to this problem is to use map function with an anonymous function:

map(arg -> arg%2==0 ? -arg^2 : arg^2+1 , x)
10-element Vector{Int64}:
    2
   -4
   10
  -16
   26
  -36
   50
  -64
   82
 -100

This works as intended.

Short-circuit evaluations

Julia exhibits lazy evaluation of formulas. This means that operations are computed only if they are needed. This property has some particular consequences for two operators, && and ||. Laziness of Julia together with necessary condition for true value in && and sufficient condition for false in || can be used for conditional evaluations. More precisely, the second (right-hand side) argument of && is evaluated if and only if the first (left-hand side) argument is true. Similarly, the second (right-hand side) argument of || is not valuated if if the first (left-hand side) argument is false.

As the second argument we can put some evaluations that we would like to conduct subject to the left-hand side argument. Simple illustrations below:

(2+2==4) && println("(2+2==4) It's true");
(2+2==4) It's true
(2+2==5) && println("(2+2==5) It's true"); # the latter is not executed as (2+2==5) is false
(2+2==4) || println("(2+2==4) It's false"); # the latter is not executed as (2+2==4) is true
(2+2==5) || println("(2+2==5) It's false");
It's false