A Taste of Go
August 14, 2014
Robert Griesemer
Robert Griesemer
Designed by programmers for programmers!
2package main import "fmt" func main() { fmt.Println("Hello, 世界!") }
package main import ( "fmt" "log" "net/http" ) func HelloServer(w http.ResponseWriter, req *http.Request) { log.Println(req.URL) fmt.Fprintf(w, "Hello, 世界!\nURL = %s\n", req.URL) } func main() { fmt.Println("please connect to localhost:7777/hello") http.HandleFunc("/hello", HelloServer) log.Fatal(http.ListenAndServe(":7777", nil)) }
const e = 2.71828182845904523536028747135266249775724709369995957496696763 const third = 1.0/3
const M64 int64 = 1<<20
const M = 1<<20
const big = 1<<100 / 1e30 // valid constant expression
Compiler complains if a constant doesn't fit where it is used.
6var x int var s, t string
var x int var s, t string = "foo", "bar" // multiple assignment var x = 42 // int var s, b = "foo", true // string, bool
x := 42 s, b := "foo", true
return &x
uint8 (byte), uint16, uint32, uint32, uint64, int8, int16, int32, int32 (rune), int64, float32, float64, complex64, complex128, uint, int, uintptr, bool, string, error // not so usual
array, struct, pointer, function, slice, map, channel
interface
[10]byte // array of 10 bytes struct { name string left, right *Node action func(*Node) } func(a, b, c int) func(http.ResponseWriter, *http.Request) error
type Weekday int type Point struct { x, y int }
[]T // slice of T
Common slice operations:
len(s) s[i] s[i:j] append(s, x) // append element x to slice s and return new slice
map[K]V // map K -> V
Common map operations:
make(map[K]V) len(m) m[k] delete(m, k)
for key, value := range m { // order of key sequence different each time }
a, b = b, a // swap f, err = os.Open(filename) if x < y { return x } else { return y } switch day { case Mon: ... // break is implicit case Tue, Wed: ... }
Syntax doesn't matter unless you are a programmer.
-- Rob Pike
Corollary:
Compactness of syntax doesn't matter unless you are reading programs.
Compact is not the same as terse. Readability is crucial.
13public static int IndexOfAny(String str, char[] chars) { if (isEmpty(str) || ArrayUtils.isEmpty(chars)) { return -1; } for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); for (int j = 0; j < chars.length; j++) { if (chars[j] == ch) { return i; } } } return -1; }
299 chars (100%), 101 tokens (100%)
14func IndexOfAny(str string, chars []rune) int { if len(str) == 0 || len(chars) == 0 { return -1 } for i, ch := range str { for _, match := range chars { if ch == match { return i } } } return -1 }
217 chars (73%), 62 tokens (61%)
Almost 30% less text and a surprising 40% fewer tokens to read!
Less clutter means reduced cognitive load.
15func Sin(x float64) float64 func AddScale(x, y int, f float64) int
func Write(data []byte) (written int, err error)
func Printf(format string, args ...interface{})
var delta int return func(x int) int { return x + delta }
// walkStdLib calls f with the filename of each .go // file in the std library until f return false. func walkStdLib(f func(filename string) bool)
Calling walkStdLib with a closure:
n := 0 println := func(s string) bool { fmt.Println(n, s) n++ return n < 10 } walkStdLib(println)
More directly:
// +build ignore,OMIT
package main
import (
"fmt"
"io/ioutil"
"path/filepath"
"runtime"
"strings"
)
func walk(dir string, f func(string) bool) bool {
fis, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}
// parse all *.go files in directory;
// traverse subdirectories, but don't walk into testdata
for _, fi := range fis {
path := filepath.Join(dir, fi.Name())
if fi.IsDir() {
if fi.Name() != "testdata" {
if !walk(path, f) {
return false
}
}
} else if strings.HasSuffix(fi.Name(), ".go") && !strings.HasPrefix(fi.Name(), ".") {
if !f(path) {
return false
}
}
}
return true
}
func walkStdLib(f func(filename string) bool) {
walk(filepath.Join(runtime.GOROOT(), "src"), f)
}
func _() {
// example START OMIT
n := 0
println := func(s string) bool {
fmt.Println(n, s)
n++
return n < 10
}
walkStdLib(println)
// example END OMIT
}
func main() {
n := 0 walkStdLib(func(s string) bool { fmt.Println(n, s) n++ return n < 10 })
}
Methods are functions with a receiver parameter:
func (p Point) String() string { return fmt.Sprintf("(%d, %d)", p.x, p.y) }
The receiver binds the method to its base type (Point):
type Point struct { x, y int }
Methods are invoked via the usual dot notation:
// +build ignore,OMIT
package main
import "fmt"
// Point START OMIT
type Point struct {
x, y int
}
// Point END OMIT
// String START OMIT
func (p Point) String() string {
return fmt.Sprintf("(%d, %d)", p.x, p.y)
}
// String END OMIT
func main() { p := Point{2, 3} fmt.Println(p.String()) fmt.Println(Point{3, 5}.String()) }
For the Weekday type:
type Weekday int
Define String method on Weekday:
func (d Weekday) String() string { // ...
// +build ignore,OMIT
package main
import "fmt"
// type START OMIT
type Weekday int
// type END OMIT
const (
Mon Weekday = iota
Tue
Wed
Thu
Fri
Sat
Sun
)
var names = [...]string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
// String START OMIT
func (d Weekday) String() string { // ...
// String END OMIT
return names[d]
}
func main() { fmt.Println(Mon.String()) fmt.Println() for d := Mon; d <= Sun; d++ { fmt.Println(d.String()) } }
Method calls via non-interface types are statically dispatched.
19Examples:
interface{} // empty interface interface { String() string } interface { Len() int Swap(i, j int) Less(i, j int) bool }
type Stringer interface { String() string }
Both Weekday and Point define a String method, so values of both can be assigned to
a variable of Stringer type:
// +build ignore,OMIT
package main
import "fmt"
type Point struct {
x, y int
}
func (p Point) String() string {
return fmt.Sprintf("(%d, %d)", p.x, p.y)
}
type Weekday int
const (
Mon Weekday = iota
Tue
Wed
Thu
Fri
Sat
Sun
)
var names = [...]string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
func (d Weekday) String() string { // ...
return names[d]
}
// Stringer START OMIT
type Stringer interface {
String() string
}
// Stringer END OMIT
func main() { var x Stringer x = Point{2, 3} fmt.Println("A", x.String()) x = Tue fmt.Println("B", x.String()) fmt.Println("C", Point{2, 3}) // fmt.Println knows about Stringer! fmt.Println("D", Tue) }
Method calls via interface types are dynamically dispatched ("virtual function call").
21package main // idents.go import ( "fmt" "os" "text/scanner" ) func main() { var s scanner.Scanner s.Init(os.Stdin) for { switch s.Scan() { case scanner.EOF: return // all done case scanner.Ident: fmt.Println(s.TokenText()) } } }
$ cat $(find $GOROOT -name '*.go') | ./idents | sort | uniq -c | sort -nr | sed 10q
A histogram is a map from statement name ("if", "for", etc.) to use count:
type histogram map[string]int
Algorithm:
func main() { h := make(histogram) walkStdLib(func(filename string) bool { h.add(filename) // does all the hard work return true }) h.print() }
func (h histogram) add(filename string) { f, err := parser.ParseFile(token.NewFileSet(), filename, nil, 0) if err != nil { panic(err) } ast.Inspect(f, func(n ast.Node) bool { if n, ok := n.(ast.Stmt); ok { // type test: is n an ast.Stmt? h[fmt.Sprintf("%T", n)]++ } return true }) }
// +build ignore,OMIT
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"path/filepath"
"runtime"
"strings"
)
func walk(dir string, f func(string) bool) bool {
fis, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}
// parse all *.go files in directory;
// traverse subdirectories, but don't walk into testdata
for _, fi := range fis {
path := filepath.Join(dir, fi.Name())
if fi.IsDir() {
if fi.Name() != "testdata" {
if !walk(path, f) {
return false
}
}
} else if strings.HasSuffix(fi.Name(), ".go") && !strings.HasPrefix(fi.Name(), ".") {
if !f(path) {
return false
}
}
}
return true
}
func walkStdLib(f func(filename string) bool) {
walk(filepath.Join(runtime.GOROOT(), "src"), f)
}
// histogram START OMIT
type histogram map[string]int
// histogram END OMIT
// add START OMIT
func (h histogram) add(filename string) {
f, err := parser.ParseFile(token.NewFileSet(), filename, nil, 0)
if err != nil {
panic(err)
}
ast.Inspect(f, func(n ast.Node) bool {
if n, ok := n.(ast.Stmt); ok { // type test: is n an ast.Stmt?
h[fmt.Sprintf("%T", n)]++
}
return true
})
}
// add END OMIT
func (h histogram) print() { // determine total number of statements total := 0 for _, count := range h { total += count } // print map entries i := 0 percent := 100 / float64(total) for key, count := range h { fmt.Printf("%4d. %5.2f%% %5d %s\n", i, float64(count)*percent, count, key) i++ } }
// main START OMIT
func main() {
// body START OMIT
h := make(histogram)
walkStdLib(func(filename string) bool {
h.add(filename) // does all the hard work
return true
})
// body END OMIT
h.print()
}
// main END OMIT
Note: Histogram (map) iteration order is not specified.
26sort.Sort operates on any type that implements the sort.Interface:
interface { Len() int Swap(i, j int) Less(i, j int) bool }
For instance, to sort a slice of strings lexically, define:
type lexical []string func (a lexical) Len() int { return len(a) } func (a lexical) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a lexical) Less(i, j int) bool { return a[i] < a[j] }
And sort:
sort.Sort(lexical(s)) // where s is a []string slice
type entry struct { key string count int } type byCount []entry func (s byCount) Len() int { return len(s) } func (s byCount) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byCount) Less(i, j int) bool { x, y := s[i], s[j] if x.count != y.count { return x.count > y.count // want larger count first } return x.key < y.key }
// +build ignore,OMIT
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
)
func walk(dir string, f func(string) bool) bool {
fis, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}
// parse all *.go files in directory;
// traverse subdirectories, but don't walk into testdata
for _, fi := range fis {
path := filepath.Join(dir, fi.Name())
if fi.IsDir() {
if fi.Name() != "testdata" {
if !walk(path, f) {
return false
}
}
} else if strings.HasSuffix(fi.Name(), ".go") && !strings.HasPrefix(fi.Name(), ".") {
if !f(path) {
return false
}
}
}
return true
}
func walkStdLib(f func(filename string) bool) {
walk(filepath.Join(runtime.GOROOT(), "src"), f)
}
type histogram map[string]int
func (h histogram) add(filename string) {
f, err := parser.ParseFile(token.NewFileSet(), filename, nil, 0)
if err != nil {
panic(err)
}
ast.Inspect(f, func(n ast.Node) bool {
if n, ok := n.(ast.Stmt); ok {
h[fmt.Sprintf("%T", n)]++
}
return true
})
}
func (h histogram) print() { var list []entry var total int for key, count := range h { list = append(list, entry{key, count}) total += count } sort.Sort(byCount(list)) percent := 100 / float64(total) for i, e := range list { fmt.Printf("%4d. %5.2f%% %5d %s\n", i, float64(e.count)*percent, e.count, e.key) } }
// byCount START OMIT
type entry struct {
key string
count int
}
type byCount []entry
func (s byCount) Len() int { return len(s) }
func (s byCount) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byCount) Less(i, j int) bool {
x, y := s[i], s[j]
if x.count != y.count {
return x.count > y.count // want larger count first
}
return x.key < y.key
}
// byCount END OMIT
// main START OMIT
func main() {
start := time.Now()
h := make(histogram)
walkStdLib(func(filename string) bool {
h.add(filename)
return true
})
h.print()
fmt.Println(time.Since(start))
}
// main END OMIT
go f() go f(x, y, ...)
func f(msg string, delay time.Duration) { for { fmt.Println(msg) time.Sleep(delay) } }
Function f is launched as 3 different goroutines, all running concurrently:
// +build ignore,OMIT
package main
import (
"fmt"
"time"
)
// f START OMIT
func f(msg string, delay time.Duration) {
for {
fmt.Println(msg)
time.Sleep(delay)
}
}
// f END OMIT
func main() { go f("A--", 300*time.Millisecond) go f("-B-", 500*time.Millisecond) go f("--C", 1100*time.Millisecond) time.Sleep(20 * time.Second) }
A channel type specifies a channel value type (and possibly a communication direction):
chan int chan<- string // send-only channel <-chan T // receive-only channel
A channel is a variable of channel type:
var ch chan int ch := make(chan int) // declare and initialize with newly made channel
A channel permits sending and receiving values:
ch <- 1 // send value 1 on channel ch x = <-ch // receive a value from channel ch (and assign to x)
Channel operations synchronize the communicating goroutines.
33Each goroutine sends its results via channel ch:
func f(msg string, delay time.Duration, ch chan string) { for { ch <- msg time.Sleep(delay) } }
The main goroutine receives (and prints) all results from the same channel:
// +build ignore,OMIT
package main
import (
"fmt"
"time"
)
// f START OMIT
func f(msg string, delay time.Duration, ch chan string) {
for {
ch <- msg
time.Sleep(delay)
}
}
// f END OMIT
func main() { ch := make(chan string) go f("A--", 300*time.Millisecond, ch) go f("-B-", 500*time.Millisecond, ch) go f("--C", 1100*time.Millisecond, ch) for i := 0; i < 100; i++ { fmt.Println(i, <-ch) } }
Mapper:
go func() { h := make(histogram) h.add(filename) ch <- h }()
Reducer:
h := make(histogram) for count > 0 { h.merge(<-ch) count-- }
func (h histogram) merge(h1 histogram) { for key, count := range h1 { h[key] = h[key] + count } }
// +build ignore,OMIT
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
)
func walk(dir string, f func(string) bool) bool {
fis, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}
// parse all *.go files in directory;
// traverse subdirectories, but don't walk into testdata
for _, fi := range fis {
path := filepath.Join(dir, fi.Name())
if fi.IsDir() {
if fi.Name() != "testdata" {
if !walk(path, f) {
return false
}
}
} else if strings.HasSuffix(fi.Name(), ".go") && !strings.HasPrefix(fi.Name(), ".") {
if !f(path) {
return false
}
}
}
return true
}
func walkStdLib(f func(filename string) bool) {
walk(filepath.Join(runtime.GOROOT(), "src"), f)
}
type histogram map[string]int
func (h histogram) add(filename string) {
f, err := parser.ParseFile(token.NewFileSet(), filename, nil, 0)
if err != nil {
panic(err)
}
ast.Inspect(f, func(n ast.Node) bool {
if n, ok := n.(ast.Stmt); ok {
h[fmt.Sprintf("%T", n)]++
}
return true
})
}
// print START OMIT
func (h histogram) print() {
var list []entry
var total int
for key, count := range h {
list = append(list, entry{key, count})
total += count
}
sort.Sort(byCount(list))
percent := 100 / float64(total)
for i, e := range list {
fmt.Printf("%4d. %5.2f%% %5d %s\n", i, float64(e.count)*percent, e.count, e.key)
}
}
// print END OMIT
// byCount START OMIT
type entry struct {
key string
count int
}
type byCount []entry
func (s byCount) Len() int { return len(s) }
func (s byCount) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byCount) Less(i, j int) bool {
x, y := s[i], s[j]
if x.count != y.count {
return x.count > y.count // want larger count first
}
return x.key < y.key
}
// byCount END OMIT
func main() { start := time.Now() h := make(histogram) walkStdLib(func(filename string) bool { h.add(filename) return true }) h.print() fmt.Println(time.Since(start)) }
// +build ignore,OMIT
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
)
func walk(dir string, f func(string) bool) bool {
fis, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}
// parse all *.go files in directory;
// traverse subdirectories, but don't walk into testdata
for _, fi := range fis {
path := filepath.Join(dir, fi.Name())
if fi.IsDir() {
if fi.Name() != "testdata" {
if !walk(path, f) {
return false
}
}
} else if strings.HasSuffix(fi.Name(), ".go") && !strings.HasPrefix(fi.Name(), ".") {
if !f(path) {
return false
}
}
}
return true
}
func walkStdLib(f func(filename string) bool) {
walk(filepath.Join(runtime.GOROOT(), "src"), f)
}
type histogram map[string]int
func (h histogram) add(filename string) {
f, err := parser.ParseFile(token.NewFileSet(), filename, nil, 0)
if err != nil {
panic(err)
}
ast.Inspect(f, func(n ast.Node) bool {
if n, ok := n.(ast.Stmt); ok {
h[fmt.Sprintf("%T", n)]++
}
return true
})
}
// merge START OMIT
func (h histogram) merge(h1 histogram) {
for key, count := range h1 {
h[key] = h[key] + count
}
}
// merge END OMIT
type entry struct {
key string
count int
}
func (h histogram) print() {
var list []entry
var total int
for key, count := range h {
list = append(list, entry{key, count})
total += count
}
sort.Sort(byCount(list))
percent := 100 / float64(total)
for i, e := range list {
fmt.Printf("%4d. %5.2f%% %5d %s\n", i, float64(e.count)*percent, e.count, e.key)
}
}
type byCount []entry
func (s byCount) Len() int { return len(s) }
func (s byCount) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byCount) Less(i, j int) bool {
x, y := s[i], s[j]
if x.count != y.count {
return x.count > y.count // want larger count first
}
return x.key < y.key
}
func init() {
n := runtime.NumCPU()
//fmt.Println(n, "cores")
runtime.GOMAXPROCS(n)
}
func main() { start := time.Now() ch := make(chan histogram) count := 0 // goroutine count walkStdLib(func(filename string) bool { count++ go func() { h := make(histogram) h.add(filename) ch <- h }() return true }) h := make(histogram) for count > 0 { h.merge(<-ch) count-- } h.print() fmt.Println(time.Since(start)) }