This is the second entry of my weekly series Learning Go. Last week I discussed the history of Go, its thought foundations, variables, and types. This week I dove into fairly familiar territory. A lot of concepts came to me quickly due to my background in JavaScript; however, it was really cool to dig into the differences in how these concepts are implemented in another language. Let’s get to it.
Control Flow, what’s that?
the order in which individual statements, instructions, or function calls an imperative program are executed or evaluate
I felt like I understood the concept of something like control flow prior to learning about its place in computer science; however, understanding its meaning paired with control structures enabled me to envision how my code is executed with much more clarity.
Essentially, this is the concept we use to determine how our code will be interpreted and ran. Control flow is broken down into three control structures:
- Sequential
- Iterative
- Conditional
Loops
a sequence of instructions that are continually repeated until a specified condition is met
Whether you have been programming for 10 months or 10 years, chances are you have probably used loops quite often. I will not spend much time going into the mechanics of how loops work, but I do want to address the fundamentals of them. You can break down what I call the three pillars of a loop fairly simply. These three pillars are an init statement, condition statement, and a post statement. Of course, we have code that is run in the loop body as long as the condition statement is true
after an iteration.
for init statement; condition statement; post statement {
// code that is executed in each iteration of the loop
}
Important Note: There are no while loops in go.
the “for” keyword
specifies a repeated execution of a block of code
When using the for
keyword, you are creating what is called a for statement. There are three forms to control iteration using a for statement:
- a single condition
- a “for” clause
- a “range”clause
Single Condition
In a single condition statement the condition after the for keyword is evaluated before an execution is ran. In order for the code to be executed, the condition must be evaluated as true
.
for 1 < 2 {
// run code
}
“for” clause
This is what most would find as the traditional for loop they use. In this use of the for keyword, we use the three pillars of a loop: init statement, condition statement, and a post statement.
package main
import (
"fmt"
)
func main() {
for i := 0; i <= 3; i++ {
fmt.Println(i)
}
// 0
// 1
// 2
// 3
}
“range” clause
A range clause is used to iterate though all entries of a slice, array, string, map, or values received from a channel (we will dive into channels in a later entry). I will demonstrate using a slice
type - we will dive deeper into this type later.
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3, 4, 5}
for i, v := range s {
fmt.Println(i, v)
}
// 0 1
// 1 2
// 2 3
// 3 4
// 4 5
// index value
}
What is happening up there?
- we assign the variable
s
to the typeslice
, thatslice
will contain values of the typeint
, those values are1, 2, 3, 4, 5
- when using a
range
clause weinit
two values: theindex
and thevalue
-i
andv
here - in each iteration of this loop
i
(position in the array) will increment by1
,v
will reflect thevalue
in the specifiedindex
of theslice
Important Note: Slices and Arrays are zero indexed - meaning the value of
index
will always start from0
, not1
Break statements
stops (terminates) execution of the innermost
for
,switch
, orselect
statement
I like to think of break statements like an escape hatch for your code. If there is a condition in which you do not want to continue to iterate, a break
statement is the best way to stop execution and move to the next piece of executable code.
A quick example:
package main
import (
"fmt"
)
func main() {
n := 0
for {
n++
if n > 5 {
break
}
fmt.Println(n)
}
}
Let me walk you through what is happening here:
- inside of the
main
function we declare the variablen
and assign it to the value0
- we use the
for
keyword to create a loop - inside of the loop we use the
++
operator to incrementn
by 1 - using an
if
statement, we evaluate if the value ofn
is great than5
- using the
fmt
package from theStandard Library
from go, we print the current value ofn
- we iterate
5 times
untiln
’s value is greater than 5 - then we use thebreak
keyword and the execution is finished
Continue statements
beings the next iteration of the innermost for loop at its post statement
I mentioned earlier that go does not have a while loop - I have found that using the continue
statement inside of a for
loop can render the same results
package main
import (
"fmt"
)
func main() {
n := 1
for {
z++
if n > 10 {
break
}
if n%2 != 0 {
continue
}
fmt.Println(n)
// 2
// 4
// 6
// 8
// 10
}
}
In the example above I am trying to find all numbers evenly divisible by 2
, let me walk you through how I am doing that using the break
and continue
statements:
- inside of the
main
function we declare a variablen
and assign it to the value1
- we use the
for
keyword to create a loop - inside of the loop we use the
++
operator to incrementn
by 1 - using an
if
statement, we evaluate if the value ofn
is great than10
, this evaluates tofalse
for the first10
iterations - next, each iteration will evaluate if the value of
n
is not evenly divisible by 2, we can determine this by using themodulo
operator - the values that are less than 10 and not evenly divisible by 2 do not step inside either
if
statement - using the
fmt
package from theStandard Library
from go, we print the current value ofn
- note: _only numbers evenly divisible by 2 be printed _ - this is because their values do not evaluate to
true
for eitherif
statement
Conditional statements
specifies the condition execution of two or more branches according to the value of a boolean expression
Conditional statements are a great way of allowing your code to have different paths of execution, depending on the outcome you desire.
A few examples of conditional statements are:
- If-then-else statements
- Else-if statements
if/else
package main
import (
"fmt"
)
func main() {
x := 1
if x == 2 {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
A fairly straight forward example. Inside of the main
function we declare a variable with the value of 1
. Next, we evaluate if the value of x
is equal to 2. It is not; therefore, we are taken to the else
statement. The else
statement is essentially a default
statement that executes code in times that the if
statement evaluates to false
.
Important Note: coming from JavaScript I am used to using the
===
operator to evaluate strict type and value comparisons, as you can see in go the operator looks like this==
.
else if
package main
import (
"fmt"
)
func main() {
x := 1
if x == 2 {
fmt.Println("equal to 2")
} else if x == 3 {
fmt.Println("equal to 3")
} else {
fmt.Println("not equal")
}
}
The only difference here is that we are adding an additional branch
that can be executed if it is evaluated to true
. Instead of two branches (as seen in the last example), we now have three. This allows you to add some dynamic aspects to your function.
All if
statements need to start with an if branch and must have an else
branch to serve as a default
; however, between those branches you are free to add as many else if
branches as you please. Although adding multiple is not advisable in most cases due to code readability and potentially performance.
Switch statements
provides multi-way execution. an expression or type specifier is compared to each case inside of the switch statement
- switch statements must have a default case
- if no expression is found for a case, it’s value is
true
- each case is compared to the value of the switch expression
package main
import (
"fmt"
)
func main() {
switch {
case false:
fmt.Println("this will not print")
case (2 == 4):
fmt.Println("this is not true")
case (4 == 5):
fmt.Println("not true either")
default:
fmt.Println("default case")
}
}
Above you can see we are creating a switch statement that contains three case statements, and they all evaluate to false
; therefore, the default
case is executed.
You can also create switch statements using a literal value or using a variable.
Here we use a literal value:
package main
import (
"fmt"
)
func main() {
switch "Yoda" {
case "Obi Wan":
fmt.Println("you don't need to see his identification")
case "Darth Vader":
fmt.Println("I am your father")
default:
fmt.Println("the chosen one, found I have not")
}
}
Here we use a variable with a single case:
package main
import (
"fmt"
)
func main() {
y := "Yoda"
switch y {
case "Luke Skywalker":
fmt.Println("your father he is")
case "Quigon Jinn":
fmt.Println("clouded this boys future is")
default:
fmt.Println("There is another skywalker")
}
}
Here we use a variable with multiple cases:
package main
import (
"fmt"
)
func main() {
y := "Yoda"
switch y {
case "Darth Maul", "Palpatine", "Mace Windu":
fmt.Println("wars make not one great")
case "Quigon Jinn":
fmt.Println("always two there are, no more no less")
default:
fmt.Println("when 900 years old you reach, look as good you will not")
}
}
In summary
This week was a great refresher on how the mechanics of how loops, the for
keyword, and conditional statements work. There is always so much more you can learn about them as well. I look forward to using these more thoughtfully in the future. Next week I will be diving into common data types in go. See you then!