Control Flow 1
Until now, we have just written some linear code that executes from top to bottom. However, in real-world applications, we often need to execute different code depending on certain states of our program. This is where control flow comes into play.
You already have an intuitive understanding of control flow. For instance, when you cross the street, you look left and right to see if any cars are coming. If there are no cars, you cross the street. Otherwise (“else”), you wait until the incoming cars have passed, then check again. This is a simple example of a decision-making process.
If Statements
The most basic form of control flow is the if
statement. An if
statement checks a condition and executes a block of
code if the condition is true. If the condition is false, the block of code is skipped.
{{ if eq 5 5 }}
{{/* It is conventional, though not required, to consistently indent inner blocks of code with spaces or tabs. */}}
Five is equal to five!
{{ end }}
We use the eq
comparison function to check whether its given arguments are equal; we will enumerate all comparison
functions in a later section on this page.
We can expand this to execute a different block of code if the condition is false by using an else
block:
{{ if eq 5 3 }}
Five is equal to three!
{{ else }}
Five is not equal to three!
{{ end }}
This can be further expanded to check multiple conditions using else if
, which are checked sequentially until one of
them is true:
{{ if eq 5 3 }}
Five is equal to three!
{{ else if eq 5 5 }}
Five is equal to five!
{{ else }}
Five is not equal to three or five!
{{ end }}
Note
The condition of an if
statement need not be a boolean: in general, an if
statement will trigger if the condition is
truthy: that is, not empty or zero. (Conversely, empty and zero values are falsy.)
For example, consider how you would check whether a string $s
is empty. One way is to explicitly compare the length of
the string to zero using if gt (len $s) 0
, but since empty strings are falsy, the more idiomatic solution is to simply
write if $s
, like so:
{{ $s := "" }}
{{ if $s }}
not empty
{{ else }}
empty
{{ end }}
Guard Clauses
As your code grows, you may find yourself nesting if
statements inside each other. This can lead to code that is hard
to read and understand. One way to avoid this is to use guard clauses. A guard clause is an if
statement that checks
for a condition and returns early via the {{ return }}
action. Therefore, the condition we checked in the earlier
if
/else
construct must be negated.
Rewriting the second example to use these guard clauses yields the following code:
{{ if ne 5 3 }}
Five is not equal to three!
{{ return }}
{{ end }}
Five is equal to three!
Although this example may be a bit contrived, guard clauses can help you avoid deeply nested code and make your code easier to read, especially when you come back to it at a later date.
Comparison Actions
In programming, we can make decisions by comparing values to each other. We can compare numbers, strings, and other data
types. The result of a comparison is a boolean value, which is either true
or false
. Normally, comparisons are
binary operators; however, in custom commands, we use functions to compare values.
Comparing across Types
Just like you cannot quite compare apples and oranges, you cannot compare values of different types. For instance, you cannot compare a number to a string. The bot will throw an error if you try to do so, you will have to convert either of them to the other type first.
We provide the following comparison functions in custom commands:
eq
(equals==
)ne
(not equals!=
)lt
(less than<
)le
(less than or equal to<=
)gt
(greater than>
)ge
(greater than or equal to>=
)
These functions can only compare basic data types as introduced in Data Types 1. Strings are compared on a byte-by-byte basis. Please refer to the functions documentation for the syntax of these functions.
Blocks And Scope
In Data Types 1, we introduced the concept of variables. Variables are used to store values and give them a name. In programming, variables have a scope, which defines where in the code the variable can be accessed. Think of each scope as a “container” for things inside it.
Often, you will want to have a variable available across multiple scopes. In custom commands, the variable is accessible
in the scope in which it was defined, as well as all nested scopes within. Let us assume that we want to assign a
coolness value, which should be true if the user’s name is “alice”. We can achieve this by defining a variable in
the outer scope and re-assigning, using the =
operator, its value in the inner scope:
{{ $isCool := false }}
{{ if eq .User.Username "alice" }}
{{ $isCool = true }}
{{ end }}
Are you cool? {{ $isCool }}
It is considered good practice to define variables in the smallest scope possible. This makes your code easier to read and understand, as you do not have to search through the entire codebase to find where a variable was defined.
Definition and Reassignment
In custom commands, you use :=
to define a variable, and =
to reassign a variable. The bot will not throw an error
if you try to re-define a variable using :=
. Rather, it will define a new variable, effectively overwriting the
existing variable, but only within that scope—the existing variable is untouched in the outer scope.
Unexpected EOF
The following error appears when you are missing an {{ end }}
action.
Each control structure must have a corresponding {{ end }}
action. If you forget to do so, YAGPDB will not know where
your control structure terminates and hence issues the above error.
To fix this error, examine each {{ if ... }}
in your program and verify that each has a matching {{ end }}
. (The
same applies to other control structures introduced in future chapters, such as {{ range ... }}
.)
If you are familiar with C-family programming languages, this error is analogous to forgetting the closing }
of a
block of code.
Exercises
Write a Custom Command to determine if the number stored in a variable
$a
is even or odd and printNumber is Even
orNumber is Odd
depending on the case. Verify the output for the following values of$a
: 1, 9, 0, 10021, -5.Predict the output of the following code snippets. If there is an error in the snippet, what is the cause of the error, and how can it be fixed? Also note down potential improvements to the code that make it easier to follow.
{{ $num1 := 10 }} {{ if $num1 }} {{ num1 := 6 }} {{ $num1 }} {{ end }} {{ if not (mod $num1 3) }} {{ $num1 }} {{ end }} {{ $num1 }}
{{ $name := "John" }} {{ if eq $name "John" }} {{ $familyName := "Walters" }} {{ end }} My name is: {{ $name }} {{ $familyName }}
{{ $mood := "happy" }} {{ if gt $mood "Sad" }} Be {{ $mood }}! {{ else }} Do not be {{ $mood }}! {{ end }}