Go - Maps, Functions cont., Structs, and Receivers

Contemporary Programming Languages - CS2001 - 7 November 2017

package main
import "fmt"
func f(x []int) {
  x[0] = 10
}
func g(x []int) {
  x = append(x, 10)
}
func main() {
  s := []int {1, 2, 3, 4, 5}
  f(s)
  t := []int {1, 2, 3, 4, 5}
  g(t)
  fmt.Println(s, t)
}
package main
import "fmt"
func f() (x int) {
  defer func() {
    x = 1
  }()
  defer func() {
    x = 2
  }()
  defer func() {
    x = 3
  }()
  x = 4
  return
}
func main() {
  fmt.Println(f())
}

Maps

Working with Maps

counts := map[string]int {"dogs": 3, "cats": 0}
counts["dinosaurs"] = 10
fmt.Println(counts["dinosaurs"]) // 10
delete(counts, "dinosaurs")

counts["giraffe"] // zero value for missing keys How can you tell if you had 0 giraffes or no value? val, ok := counts["giraffe"] // ok is true if key exists, false if not in map

if val, ok := counts["giraffe"]; ok {
  // case that the key exists
} else {
  // case that the key doesn't exist
}

Functions Cont.

  • Arguments are passed by value
  • Need to change a value in the calling function? Use a pointer.
  • There’s no pass by reference
package main
import "fmt"
func f(x int) {
  x++
}
func g(x *int) {
  (*x)++
}
func main() {
  var a, b int
  f(a)
  g(&b)
  fmt.Println(a, b)
}

Function Values

  • Functions are values, too.
  • They can be passed to functions
  • They can be returned from functions
package main
import(
  "fmt"
  "math"
)
// fn is var name, then type of variable is func(), then return of passed function
func compute(fn func(float64, float64) float64) float64 {
  return fn(3, 4)
}
func main() {
  hypot := func(x, y float64) float64 {
    return math.Sqrt(x*x, y*y)
  }
fmt.Println(hypot(5, 12)) // 13
fmt.Println(compute(hypot)) // 5
fmt.Println(mat.Pow) // 81
}

Function Closures

  • Go functions can have closures
  • A function can still reference variables from an outer function’s scope, even after the outer function has returned
package main
import "fmt"
func adder() func(int) int {
  sum := 0
  return func(x int) int {
    sum += x
    return sum
  }
}
func main() {
  a := adder()
  for i := 0; i < 10; i++ {
    fmt.Println(a(i))
  }
}
// 0 1 3 6 10 15 21 28

Structs

  • A collection of fields
type Pony struct {
  Name           string
  Height, Weight float64
  FavoriteFoods  []string
}
func main() {
  dave := Pony {"Dave", 3.2, 100, []string {"pie"}}
  alice := Pony {Name: "Alice", Weight: 100, Height: 3.2, FavoriteFoods: []string {"Kale"}}
  carol := Pony {Name: "carol"}
  e := Pony {}
  p := &Pony {} // *Pony
  p2 := new(Pony)

  dave.Name = "Davey"
}
// Implicit dereference
p.Name = "Peter"

fmt.Println(dave) // {Davey 3.2 100 [pie]}

Exporting

  • Items (funcs, structs, struct fields, constants, variables, etc.) that start with a capitol letter are exported from a package
  • If you import a package, you can only access exported items
  • We’ll talk about packages more later.

Methods

  • Go does not have classes
  • But, you can define methods on types (including structs)
func(p Pony) PrintFavorites() {
  fmt.Println(p.Name, "likes", strings.Join(p.FavoriteFoods, ","))
}
func main() {
  dave := Pony {"Dave", 3.2, 100, []string {"carrot", "broccoli"}}
  dave.PrintFavorites() // dave likes carrot, broccoli
}

Value Recievers

  • Methods w/ value receivers operate on copies of the original value
  • Changing attributes of the received value does nothing to the original

Pointer Receivers

  • Methods can modify the value to which the reveiver points
  • More common than value receivers
func(p *Pony) AddFavorite(f string) {
  p.FavoriteFoods = append(p.FavoriteFoods, f)
}
func main() {
  dave := <dave details>
  dave.AddFavorite("spaghetti")
  fmt.Println(dave)
  dave.PrintFavorites() // ... carrot, broccoli, spaghetti
}

Which to choose?

  • Use pointer receivers to:
    1. Modify the value pointed-to by the receiver
    2. Avoid copying big data structures
  • Whichever you choose, all methods of a given type should be the same. That is, all pointer receivers or value receivers, not a combination of both.