Lab 12

Deadline: EOD Tuesday, April 23rd

Objectives:

  • TSW learn about Go, the programming language
  • TSWBAT write simple Go programs and about introductory Go functionalities and syntax
  • TSW learn about Go subroutines and channels
  • TSWBAT make simple tests using Go functionalities

Setup

Pull the Lab 12 files from the lab starter repository with

git pull staff master

Go

Hopefully by now, you have realized that Go is a programming language, one that we will utilize for the fifth and last project of this course. As a result, this lab will aim to try to familiarize you with some of the intricacies of Go and allow you to practice reading/writing some Go code. This lab takes pieces from this tutorial. We would recommend going through this tutorial fully to get a better grasp of the different functionalities Go has to offer.

If you would like to develop on an IDE for Go, check out this link.

Why Go?

Go was designed from the ground up to support concurrency. Second, Go has very robust and easy-to-use testing tools. Finally, Go is a fairly new language that tries to find a happy medium of performance (through compilation) and safety (through strict typing and garbage collection). Go is very similar to C in many ways so it should look fairly familiar to you (like learning Italian if you already know Spanish). However, it is also not so similar to C in many ways so that this lab is to help bridge that gap!

Installing Golang

While we recommend coding on the Hive machines, as they already have Go installed and setup for you, you may install it locally if you prefer. Follow the instructions here and find the instructions that correspond with your OS.

You can visualize if you have Go installed and the version of your installation by running

go version

Packages

Go programs consist of numerous packages, all of which need to be imported in order to be used. There are two (somewhat similar) ways of importing packages. One is to simply list out packages with an import as such

import "fmt"
import "os"
import "strings"

Or equivalently

import (
	"fmt"
	"os"
	"strings"
	)

Note: Additionally, at the start of every file, you must include package main, as this will define the package that this file is part of. Naming main as the name of the package and the function tells Go that this is where the program’s execution should start. You need to define a main package and main function even when there is only one package with one function in the entire program.

Exercise 1 - Creating A Simple Function

Here we will explore creating and running a very common program, the hello world program. In your lab12 directory, open the file called exercise1.go. Next, as described above, import the fmt package and also define the main package and function. Next, we wish to use the Println function of the package fmt. We can do this by taking the package name, then appending it with the function name with a period in the middle as such

fmt.Println()

From there, insert the quoted string "Hello World!" and save your file.

Next, let’s create a function in the same file called random that prints a “random” integer from [0, 10) and takes in no arguments. You will need both the fmt and math/rand packages. In the math/rand package, you will find a function called Intn(), in which you pass in an integer. Your task is to create a function that prints out "Your random number of the day is" followed by the output of Intn(). Then, call this function after the Println in the main function.

Next, we want to run our code! There are two ways to build and run your code

go build exercise1.go
./exercise1

or you can quickly build and run it by

go run exercise1.go

and hopefully you get the following as an output

Hello World!
Your random number of the day is 1

Variables and Datatypes

Typing in Go is slightly different, both in declaration and in function handles. In order to create a function square that takes in an integer n and spits out the square, the handle must look like func square(n int) int where you follow the argument type after the name and the function return type at the very end.

If we want to create a variable, x, that holds an integer, you would declare it like var x int, just like Python, we can assign multiple in a single equality like var x, y = 10, 15. Notice that we didn’t include a type, if we initialize the variable when we declare it, the variable will simply take the type of the initializer, just like Python! Within a function, we don’t even need the var and can instead use x := "Hello", but this can only be done within functions.

Exercise 2 - Creating A More Complex Function

For this part, we will be modifying the file exercise2.go.

Now that we have some familiarity with Go, your job is to create a function called print_squares that prints out the square of every number from 1 through n, all on the same line as such (example is called on n = 5)

1 4 9 16 25

Hint: you may need the fmt.Printf() function, read more here. You may also need the syntax for a for loop, which you can read about here. Additionally, there are NO while loops in Go, there are only for loops with only the for [CONDITIONAL] {}.

Next, create a function called print_even_squares that prints out the square of every even number from 1 through 10, all on the same line as such (example is called on n = 5)

4 16

Finally, to spice things up, create a function called fibonacci that prints the first n fibonacci numbers, starting with the first indexed number (example is called on n = 5)

1 1 2 3 5

Goroutines and Channels

A goroutine is a function that is capable of running concurrently with other functions. To create a goroutine we use the keyword go followed by a function call. If you take a look at goroutines.go you’ll see an example of how to invoke these routines. Go routines can be thought of lightweight threads that can execute functions concurrently to the main runtime stream.

Another huge feature of Go is the channel functionality, which allows us to send and receive values across routines. Take a look at gochannels.go to see an example of the utilization of channels. It is important to note that you must always create a channel before using it, and we utilize channels using the <- operator. For example, if we let our channel be called channel

channel <- 10

This would be putting the value 10 into the channel, while the following receives from the channel and assigns it to variable x

x := <- channel

Additionally, when sending or receiving through a channel, the operations will “block”, which means it will wait until the value is received if sending and will wait until a value is received if receiving, which effectively allows goroutines to synchronize as the routine will not be able to continue until its complementary action is performed.

Additionally, we can create a buffered channel, which mean that sends to a buffered channel block only when the buffer is full. and receives block when the buffer is empty. What do you think happens if we try to overfill the buffer?

Another functionality of channels is the ability to close channels. Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression

// Sender called close(ch)
x, closed := <-ch

If closed is false, this means that there are no more values to receive and the channel has been closed by the sender. NOTE that only the SENDER should close a channel, never the receiver.

Another important functionality in Go is the select statement, specifically in the context of channels. The select statement causes a goroutine to wait on multiple channels until one is ready. Otherwise, the select statement will block until one of its cases can run, and will then execute that case. If multiple are ready, then it chooses one at random. Take a look at the file goselects.go and notice how we can effectively take input from different channels, that can have different meanings if a value comes through it. (HINT: This will be very useful for project 5! Make sure you understand how it works and please ask a TA if you have any questions about it.)

Exercise 3 - Creating a Routine Function

For this exercise we will ask you to implement a function called return_range(n int, c chan int) inside the file exercise3.go. This function will loop from 1 to n (inclusive) and send every value of i into the channel and then afterwards close the channel. Then, in the main function, your job is to create a subroutine for return_range and calculate the sum of each number from 1 to size, where each number has been squared and incremented by 1. Take a look here for a nice way to loop over all the items in a channel until it is closed.

Arrays, Slices, and Maps

In Go, we can declare an array like [n]T is an array of n values of type T. The expression var a [10]int declares a variable a as an array of ten integers. It is also important to note that arrays cannot be resized. You can index into an array using bracket notation a[0] and a[1]. You can also declare values at the time of creation like a := [3]int{1, 2, 3}.

Additionally, just like in Python, we can slice an array using two numbers such as a[low_index:high_index] which selects the indices starting with low_index, inclusive, and ending with high_index, exclusive. However, unlike Python, slices do not copy values, but instead only reference the values in the array, so if we were to change a[low_index] after assigning the slice to another variable, the variable would see the change in value. You can read more about arrays and slices here!

Finally, maps in Go are very similar to dictionaries in Python or HashMaps in Java, allowing us to store (key, value) pairings. You can read more about it and code some examples here.

Exercise 4 - Testing!

One of the big features of Go is the fact that it allows for very easy creation of tests. Take a look at functions_test.go inside the testing/ directory for an example of how to format tests. Once you understand the test file, run the test suites by running

go test

The test functions must start with the string Test and also be passed in t *testing.T as an argument. Your job is to create another test that will test our AbsSum function with negative inputs. Additionally, if you run go test -cover you will see that even after writing our test for negative inputs we have low test coverage. The -cover flag shows what percentage of lines of your code actually get run by your tests. Your task is to add enough tests so that running the tests with the cover flag passes with 100% of statements.

Checkoff

  • Show your output of go run exercise1.go
  • Show your output of go run exercise2.go
  • Show your output of go run exercise3.go
  • Show your output of go test and go test -cover from within the testing/ folder.