User Tools

Site Tools


go_tour_notes

STUFF (tour)

  • explain early that functions are not polymorphic (basics section)
  • earlier: type aliasing (which introduces a new type, not just an alias, except for byte and rune)
  • swap first two pages of Methods section
  • I would move closures later (possibly after methods)

Welcome

  • “go tool tour” doesn't work on Fedora, why not?
  • offline tour runs ${GOPATH}/bin/gotour
  • “package main” for commands

Packages

Basic packages

  • Every Go program is made up of packages, starts at main package.
  • By convention, the package name is the same as the last element of the import path.
  • import “math/rand” imports from math/rand/rand.go:
    package rand
    
    import (
            "math"
    )
  • “factored” import statement:
    import (
    	"fmt"
    	"math"
    )

Exported stuff

  • name is exported if it begins with a capital letter:
    func main() {
    	fmt.Println(math.Pi)
    }
  • From math/const.go:
    // Mathematical constants.
    const (
            E   = 2.71828182845904523536028747135266249775724709369995957496696763 // https://oeis.org/A001113
            Pi  = 3.14159265358979323846264338327950288419716939937510582097494459 // https://oeis.org/A000796
            Phi = 1.61803398874989484820458683436563811772030917980576286213544862 // https://oeis.org/A001622
            ...
           )

Functions

Basic functions

func add(x int, y int) int {
	return x + y
}

Collapse return types

func add(x, y int) int {
	return x + y
}

Multiple return values

func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

Named return values

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

func main() {
	fmt.Println(split(17))
}

Naked return statements should be used only in short functions, as with the example shown here. They can harm readability in longer functions.

Variables

Types

bool
string
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
     // represents a Unicode code point
float32 float64
complex64 complex128

Declarations

Type of list of variables is at the end:

var c, python, java bool

Initializers

var i, j int = 1, 2

Inside functions, implicit short declarations using :=:

func main() {
	var i, j int = 1, 2
	k := 3
	c, python, java := true, false, "no!"

	fmt.Println(i, j, k, c, python, java)
}

Can be “factored” into blocks:

package main

import (
	"fmt"
	"math/cmplx"
)

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

Uninitialized variables are given their corresponding zero value:

  • 0 for numeric types,
  • false for the boolean type
  • “” (the empty string) for strings

Type conversions

No implicit type conversion, must be explicit:

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

i := 42
f := float64(i)
u := uint(f)

Print the type of a variable:

func main() {
	v := 42.1 // change me!
	fmt.Printf("v is of type %T\n", v)
}

Constants

const Pi = 3.14

Flow control

for loop

Go's only looping construct:

func main() {
	sum := 0
	for i := 0; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}
	for ; sum < 1000; {
		sum += sum
	}

Here's Go's while loop:

	for sum < 1000 {
		sum += sum
	}

Go's infinite loop:

	for {
	}

if

func sqrt(x float64) string {
	if x < 0 {
		return sqrt(-x) + "i"
	}
	return fmt.Sprint(math.Sqrt(x))
}

Open with a short initializer statement:

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

Variables declared inside an if short statement are also available inside any of the else blocks.

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	} else {
		fmt.Printf("%g >= %g\n", v, lim)
	}
	// can't use v here, though
	return lim
}

switch

Go's switch cases need not be constants, and the values involved need not be integers.

Top to bottom, executes only one possibility:

func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.", os)
	}
}

Switch with no condition same as switch true – cleaner to write if-then-else:

func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

defer

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

Multiple defers are stacked so executed in reverse order:

func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}

	fmt.Println("done")
}

Pointers

Go has no pointer arithmetic; zero value is nil.

Declaring simple pointers:

var p *int

Initialize at the same time with :=:

func main() {
	i, j := 42, 2701

	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 37   // divide j through the pointer
	fmt.Println(j) // see the new value of j
}

Structs

Structs

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

Struct fields

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}

Pointers to structs

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9  // no need to dereference
	fmt.Println(v)
}

Struct literals

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

Arrays

Simple declaration (arrays cannot be resized):

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}

Slices

Basic declaration

An array has a fixed size. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array. In practice, slices are much more common than arrays.

The type []T is a slice with elements of type T:

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

Slices are simply references to arrays

A slice does not store any data, it just describes a section of an underlying array.

Changing the elements of a slice modifies the corresponding elements of its underlying array.

Other slices that share the same underlying array will see those changes.

Slice literals

func main() {
	q := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(q)

	r := []bool{true, false, true, true, false, true}
	fmt.Println(r)

	s := []struct {
		i int
		b bool
	}{
		{2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, true},
	}
	fmt.Println(s)
}

Slice defaults

a[0:10]
a[:10]
a[0:]
a[:]

Slice length and capacity

The length of a slice is the number of elements it contains.

The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.

The length and capacity of a slice s can be obtained using the expressions len(s) and cap(s).

Nil slices

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("nil!")
	}
}

Creating a slice with make

func main() {
	a := make([]int, 5)
	printSlice("a", a)

	b := make([]int, 0, 5)
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[2:5]
	printSlice("d", d)
}

Slices of slices

func main() {
	// Create a tic-tac-toe board.
	board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}

	// The players take turns.
	board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"

	for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
}

Appending to a slice

Range

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}
func main() {
	pow := make([]int, 10)
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	for _, value := range pow {
		fmt.Printf("%d\n", value)
	}
}

Maps

Function closures (I would do this later)

func adder() func(int) int {
	sum := 0             // closure part
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

Fibonacci closure:

func fibonacci() func() int {
    current, next := 0, 1
    return func() int {
        current, next = next, next+current

        return current
    }
}

Methods

Non-method Abs() function

Non-method Abs() function for Vertex, which takes a Vertex as a regular argument (no polymorphism)

type Vertex struct {
	X, Y float64
}

func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(Abs(v))
}

Basic method

Go does not support classes; rather, you can define methods on types by adding a receiver argument to a function:

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {        // receiver argument
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())            // style 1
        fmt.Println(Vertex{3,4}.Abs())  // style 2
}

Methods and type "aliasing"

You can declare a method on non-struct types, too. Below, we see a numeric type MyFloat with an Abs method:

type MyFloat float64   // introduces a new type, not just an alias

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).

Methods with pointer receivers

Refresher: call-by-value

Call-by-value function, which makes a copy of the argument:

package main

import (
        "fmt"
)

type Vertex struct {
        X, Y float64
}

func Scale(v Vertex, f float64) Vertex {
        v.X = v.X * f
        v.Y = v.Y * f
        return v
}

func main() {
        v := Vertex{3, 4}
        fmt.Println(v)
        fmt.Println(Scale(v, 10))
        fmt.Println(v)
}

{3 4}
{30 40}
{3 4}

Refresher: pointer-based function

Pointer-based call:

package main

import (
        "fmt"
)

type Vertex struct {
        X, Y float64
}

func Scale(v *Vertex, f float64) {
        v.X = v.X * f
        v.Y = v.Y * f
}

func main() {
        v := Vertex{3, 4}
        fmt.Println(v)
        Scale(&v, 10)
        fmt.Println(v)
}

{3 4}
{30 40}

Notes about the above:

  • Even if the arg is a pointer, Go allows you to use “.” to dereference.
  • You cannot support both variations of that function
go_tour_notes.txt · Last modified: 2018/04/14 08:57 by rpjday