Until now, all of our code executed in a sequential manner - the first line was run, then the next one, and so on until the last line is executed. What if we wanted to execute some lines only when a certain condition arises? That's what decision statements are for.
There are two types of decision statements in Julia:
- if-elseif-else statements
- ternary operators
Note that Julia, unlike other languages, does not have switch-case statements.
In addition we will discuss "short curcuit evaluation" which can be used as a type of decision statements. Finally, we'll introduce flowcharts as a tool to make if-elseif-else statements more understandable.
Boolean Conditions¶
Central to decision statements (and, as we'll see, to loops) is the idea of a boolean condition, which is shortened to condition. This is just a statement that evaluates to either true
or false
.
The simplest conditions involve comparisons between numbers. For two numbers a and b,
- a < b means that a is strictly less than b
- a <= b means that a is less than b
- a == b means that a is equal to b in value
- a != b means that a is not equal to b
- a >= b means that a is greater than or equal to b
- a > b means that a is strictly greater than b
There is also a strict equality:
- a === b means that a is equal to b in value and type. More on this in a second.
Finally, there are an approximate equality and an approximate inequality, which will not be discussed in this post:
- a ≈ b entered by typing \approx [TAB]; means that a is approximately equal to b
- a ≉ b entered as \napprox [TAB]; means that a is approximately equal to b
Note the difference between =
and ==
:
x = 5
assigns the value 5 to x; the value of x may be changedx == 5
compares the value ofx
with 5; the value of x doesn't change
Here are some examples using numbers:
a = π # Entered by typing \pi [TAB]
b = 4.0
c = 10
# Simple examples
println(a < 6)
println(a <= b)
println(b == 5)
println(c != 11)
println(a >= b)
println(c > 9.5)
true true false true false true
More complicated examples:
println(a * b > 15)
println(cos(a) == -1)
println(cosd(45) == sqrt(2)/2)
false true true
There are two types of equality in Julia:
- c == d
- c === d
The difference is that the latter compares both the values of c
and d
as well as the type, whereas the former only tests the values of c
and d
. For example:
c = 4
d = 4.0
println(c == d) # Returns true since c and d have the same numeric value
println(c === d) # Returns false since c is an Int64 whereas d is a Float64
true false
Conditionals aren't just limited to numbers:
n = "Sam"
println(n == "Bob")
println(occursin("na", "Pennsylvania"))
false false
LaTeX-like symbols can be entered into the Julia REPL or into Jupyter notebooks; here are some that are related to comparisons:
println(a ≤ b) # Same as a <= b. Enter by typing \le [TAB]
println(a ≥ b) # Same as a >= b. Enter by typing \ge [TAB]
println(a ≠ b) # Same as a != b. Enter by typing \ne [TAB]
true false true
Boolean Connectives¶
More complicated conditions can be built from simpler ones by using the three Boolean connectives: and, or, and not. These are also called Boolean operators.
Let p
and q
be variables holding boolean values; then:
- The whole statement
p && q
is true should bothp
andq
be true, otherwise it is false p || q
is true should eitherp
orq
, or both, be true!p
is read "not p", and the whole statement is true shouldp
be false, and it is true otherwise.
This can all be summarized using truth tables:
A condition of the form p && q
is called a conjunction, and the parts conjoined together (namely the p
and the q
) are called conjuncts.
Similarly, p || q
is called a disjunction and the p
and the q
are called disjuncts.
For example:
a = π # Entered by typing \pi [TAB]
b = 4.0
c = 10
println(3 < a && a < 22//7)
println(b == 4 || c < 8)
println(!(b <= 4.5)) ## This is the same as b > 4.5
true true false
One of the niceties of Julia is that continued inequalities can be used. For example, 3 < a && a < 22//7
means that a is strictly between 3 and 22/7. Anybody who knows a little math would write this as 3 < a < 22//7
, and this is indeed a valid Julia condition:
println(3 < a < 22//7)
true
The if-elseif-else Statement¶
The simplest form of the if-elseif-else statement looks like this:
if condition
statement1
statement2
...
end
The idea is that when control reaches the if condition
line, the condition is evaluated. If it is true, then statement1, statement2, and all the other statements before the end
, are executed. But if the condition is false, those statements are skipped. This situation is sometimes described by saying that the block of code consisting of statement1, statement2, etc., are guarded by the condition.
To help understanding control flow, flowcharts are helpful. Here's a flowchart of the above code:
An example of this simple decision statement is:
user = "Sam"
if user == "Bob"
println("Hello, Bob!")
println("Welcome to the 'guarded' part of this if statement")
end
In this example, user == "Bob"
is the condition. Since the user
is Sam, the condition is false. So the above code outputs nothing!
If we changed the user's name, we can force control into that block:
user = "Bob"
if user == "Bob"
println("Hello, Bob!")
println("Welcome to the 'guarded' part of this if statement")
end
Hello, Bob! Welcome to the 'guarded' part of this if statement
"Wait," you say, "the value of user
is already known! Why not just dispense with the if-end completely?"
Well, the value of user
is known in these two examples, but in general it will not be known - the user may enter his or her name in a form, the name may be read from a database, and etc.
Let's move on to something a little more complicated. The if-else decision statement allows us to perform some actions when the condition is true, and other actions when the condition is false. It looks like this:
if condition
statementA1
statementA2
...
else
statementB1
statementB2
...
end
When control reaches the if condition
statement, the condition is evaluated. Should it be true, then statementA1
, statementA2
, etc., are executed. If the condition is false, then statementB1
, statementB2
, etc., are executed. These two alternatives are called branches.
Here's how it will look as a flowchart:
The lines after the else
and before the end
are sometimes called the default case, since they are executed anytime the condition is false.
user = "Bob"
if user == "Sam"
println("Hello, Bob!")
println("Welcome to the 'true' part of this if statement")
else
println("Hello, user!")
println("Welcome to the 'false' part of this if statement")
end
Hello, user! Welcome to the 'false' part of this if statement
What if we have three or more alternatives? That's what if-elseif-else statements are for:
if condition1
statementA1
statementA2
...
elseif condition2
statementB1
statementB2
...
else
statementC1
statementC2
...
end
This has only two conditions in it, but there can be more. Imagine the above example with Bob and Sam was part of a commerce or banking application; actual commerce applications are immensely more complicated, but go with it...
user = "Sally"
userIsKnown = false
balance = 0
if user == "Bob"
userIsKnown = true
balance = 1500
elseif user == "Sally"
userIsKnown = true
balance = 1300
elseif user == "Sam"
userIsKnown = true
balance = -5
else
userIsKnown = false
balance = 0
end
if userIsKnown
println("Hello $(user), your balance is $(balance) dollars.")
else
println("Welcome new customer!")
end
Hello Sally, your balance is 1300 dollars.
Nested Decision Statements¶
Decision statements can be nested inside each other. Continuing with the banking example:
user = "Sam"
userIsKnown = false
balance = 0
if user == "Bob"
userIsKnown = true
balance = 1500
elseif user == "Sally"
userIsKnown = true
balance = 1300
elseif user == "Sam"
userIsKnown = true
balance = -5
else
userIsKnown = false
balance = 0
end
if userIsKnown
println("Hello $(user), your balance is $(balance) dollars.")
if balance < 0
println("You are overdrawn! Do you want to set up a payment plan?")
end
else
println("Welcome new customer!")
end
Hello Sam, your balance is -5 dollars. You are overdrawn! Do you want to set up a payment plan?
x
const MIN_BALANCE_FOR_HIGH_INTEREST_ACCOUNT = 1000
user = "Bob"
userIsKnown = false
hasHighInterestAccount = false
balance = 0
if user == "Bob"
userIsKnown = true
balance = 1500
hasHighInterestAccount = false
elseif user == "Sally"
userIsKnown = true
balance = 1300
hasHighInterestAccount = true
elseif user == "Olivia"
userIsKnown = true
balance = 900
hasHighInterestAccount = false
elseif user == "Sam"
userIsKnown = true
balance = -5
hasHighInterestAccount = true
else
userIsKnown = false
end
if userIsKnown
println("Hello $(user), your balance is $(balance) dollars.")
if balance < 0
println("You are overdrawn! Do you want to set up a payment plan?")
elseif balance > MIN_BALANCE_FOR_HIGH_INTEREST_ACCOUNT && !hasHighInterestAccount
println("Would you like to set up a high interest account?")
end
else
println("Welcome new customer!")
end
Hello Bob, your balance is 1500 dollars. Would you like to set up a high interest account?
Notice that in this inner decision statement, the else
is omitted. In general, the else
is optional, but noticed that we initialized userIsKnown
, hasHighInterestAccount
, and balance
are initialized at the top.
Ternary Operators¶
Ternary operators are like compressed if-else statements. They look like this:
condition ? result1 : result2
The ides is that if the condition is true, result1
is the value of the whole statement, otherwise result2
is the value of the whole statement. Here's an example:
a = 8
b = a > 17 ? 100 : 50
println("b = ", b)
b = 50
This is one application of ternary operators: to quickly assign values to variables. Another application looks like this:
condition ? statement1 : statement2
If condition
is true, then statement1
is performed, otherwise statement2
is performed.
Here's an example where the actions are println statements:
a = 5
a == 5 ? println("a is 5") : println("a is not 5")
a is 5
In fact, the b = a > 17 ? 100 : 50
example can be rewritten as:
x
a = 8
a > 17 ? b = 100 : b = 50
println("b = ", b)
b = 50
Ternary operators can be nested, but it is hard to read:
balance = 2500
balance < 0 ? println("You have a negative balance, do you want to set up a payment plan?") :
balance < MIN_BALANCE_FOR_HIGH_INTEREST_ACCOUNT ? println("Your balance is $(balance) dollars,") :
println("You should set up a high-interest account!")
You should set up a high-interest account!
Short-Circuit Evaluation¶
We've seen how two conditions can be combined using Boolean connectives, and how the combination is evaluated. For example, p && q
is true should both p
and q
be true. That being said, what should we do if p
is false? Should we (or, rather, should Julia) check q
's value? No: the value of q
doesn't change the final value of p && q
since p
is false.
This idea - that it is possible to stop the evaluation of complex conditionals early - is called short-circuit evaluation.
We saw how the evaluation of p && q
can be short-circuited should we know that p
is false. Something similar happens with disjunction: p || q
is true should p
be true, and we don't need to bother evaluating q
.
Why is this important?
First, this speeds-up the evaluations of conditionals. Imagine we had a dozen conjuncts in a condition. The moment we figure out that one of the conjuncts is false, we can halt the evaluation since the complete condition must be false. Another way of getting improved performance using short-circuit evaluation is that some of the components may be time consuming to evaluate. By arranging the components in the right way, we can speed up the evaluation of the whole condition.
A second reason for using short-circuit evaluation is that they give us another type of decision statement, even shorter than the ternary operator.
This comes by looking at short-circuit evluation from the opposite direction: the evaluation of p && q
must continue on to q
if p
is true.
So with p && q
:
- should
p
be true, we must go on to evaluateq
- should
p
be false, we skip the evaluation ofq
.
Combine this with the fact that q
can also be a statement, then p && q
works just like first decision statement we examined! Here's an example:
balance = 3450.00;
# The println statement must be evaluated since balance > 1000 is true
balance > 1000 && println("You're eligable for a high-interest account!")
You're eligable for a high-interest account!
balance = 148.98
# The println statement is skipped since balance > 1000 is false
balance > 1000 && println("You're eligable for a high-interest account!")
false
In this last example, false
is displayed since that's the result of the last evaluation done.
Conclusion¶
In this post we saw two types of decision statements, if-elseif-else and the ternary operator. We also saw how short-circuit evaluation can give us a third type of decision statement.
Decision statements can become complicated when they are nested inside each other, and the best way to make those nested statements understandable is to draw a simple flowchart.
And that's pretty much all there is to decision statements! They're simple to understand but extremely useful, as we'll see in later posts!