gothamgo 2018 workshop morning

gothamgo 2018 workshop morning

Welcome

History

Decisions made

  1. for example: there is no operator overloading, because it can result in weird behaviors. I call it “glancibility”, if you can make + operator do some
  2. another example: there’s not base class with no type

it will feel limited but you will end up with code that is familiar, to the extent that if I look at another programmer’s code it looks like I wrote it

Getting Started With Go

PATHS

GOPATH Add the bin to the PATH

Common Layout

$GOPATH/src/github.com/username/project

Syntax and Types

keywords

fallthough - in a switch it means it will not break, but also go to the next case. It’s a bit confusing and I never use it

operators and delimiters

:=

without initialization

var a int

explicit type

var a int = 1

implicit type

a:=1

data types

uint/int - either 32 or 64 bits depending on the system

strings

zero values, constant types

iota

const (
  _  = 1 << (iota * 10) // ignore the first value
  KB                    // decimal:       1024 -> binary 00000000000000000000010000000000
  MB                    // decimal:    1048576 -> binary 00000000000100000000000000000000
  GB                    // decimal: 1073741824 -> binary 01000000000000000000000000000000
)

Structs

type User struct {
  Name string
  Email string
}

...

func main() {
  foo := User{}
  User.Name = "foo"
  User.Name = "bar"
}

A more compact way to initialize the struct

u := User {
  Name: "foo",
  Email: "bar", //note you must provide this second comma here or the linter will complain
}

Caveat: there is no constructor. You can set as many of the field values on a struct at initialization time as you want.

u := User{Email: "marge@example.com"}
u.Name = "Marge Simpson"

scope

Arrays and Iterations

Initialization

names := [4]string{}
names[0] = "John"

Array Types

Matrix

type Matrix [3][3]int

m := Matrix{
    {0, 0, 0},
    {1, 1, 1},
    {2, 2, 3},
  }

Loops

  names := [4]string{"John", "Paul", "George", "Ringo"}

  for i := 0; i < len(names); i++ {
    fmt.Println(names[i])
  }

Loop forever, continue or break at a condition

for {
  if i == 3 {
    // go to the start of the loop
    continue
  }

  if i == 4 {
    break
  }
}

Range, much more useful than for loop

func main() {
  names := [4]string{"John", "Paul", "George", "Ringo"}

  for i, n := range names {
    fmt.Printf("%d - %s\n", i, n)
  }

Slices

nameSlice :=[]string{'foo','bar'}

Slice internals

Think of slice as having three members

Make

anotherSlice := make([]string, 1) //specify length of 1
yetAnotherSlice := make([]string, 1, 3) //specify length of 1 and capacity of 3

If you know how big the slice will grow to be, you can allocate extra capacity to save some memory, but it can be tempting to optimize too early because the bottleneck are often I/O or waiting for user input

Append

append(nameSlice, 'baz')

...
// append entire other slice
append(nameSlice, anotherSlice...)

Should the slice not have enough space Go will automatically reallocate the slice to have more capacity, growth factor is always 2, the old one is garbage collected.

2D slices

// a slice of byte slices
type Modules [][]byte //note instead of int/string like a matrix, you have a byte

course := Modules{
  []byte("Chapter One: Syntax"),
  []byte("Chapter Two: Arrays and Slices"),
  []byte("Chapter Three: Maps"),
}

Mutate vs. Copy

Danger, changing a slice subset will mutate the original slice, because the underlying arrays are mutated. This might lead to be unexpected behavior or bugs.

names := []string{"John", "Paul", "George", "Ringo"}

fmt.Println(names) // [John Paul George Ringo]

guitars := names[:3]

fmt.Println(guitars) // [John Paul George]

for i, g := range guitars {
  guitars[i] = strings.ToUpper(g)
}

fmt.Println(names) // [JOHN PAUL GEORGE Ringo]

Use copy

  veggies := []string{"carrot", "potato", "cucumber", "onion"}
  v := veggies

  v2 := make([]string, len(veggies))
  copy(v2, veggies)