mitsu's techlog

通信ネットワークの研究してる大学院生です。備忘録も兼ねて技術系の話を適当に

A Tour of GoでGoに入門した

Goの勉強にはまずA Tour of Go読むと良いっぽかったので読みました。
途中途中に練習問題があって、それを解きながら進めると適度に手も動かせて良かったです。

以下は自分用のまとめです。
最後に練習問題の解答をまとめて載せています。
(合ってるかは保証できません)

Packages, variables, and functions

  • importはまとめて書くのがGo流
import (
	"fmt"
	"math"
)
  • 大文字から始めると他から参照可能。小文字からだと外からはアクセスできない。
  • 関数の宣言は"func 関数名 (変数名 型, 変数名 型) 返り値の型"。型が同じ時はまとめてok。返り値は複数でもok。
func add(x int, y int) int {
	return x + y
}
func add(x, y int) int {
	return x + y
}
func swap(x, y string) (string, string) {
	return y, x
}
  • 返り値にあらかじめ名前をつけておくこともできる。その場合returnだけでよし。
func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}
  • "var 変数名 型"で変数宣言。初期値を与えないときはゼロ値が入る。
  • 初期化するときは"変数名 := 値"で宣言と初期化をまとめてできる。型は推論される。
var i, j int
k := 3
  • 型変換は"型名(値)"で可能。
  • constキーワードを用いると定数を宣言可能。定数の時は":="でなく"="。

Flow control statements: for, if, else, switch and defer

  • For文は括弧無しversion。式の省略も自由
for i := 0; i < 10; i++ {
    sum += i
}
for ; sum < 1000; {
    sum += sum
}
  • While文はないのでFor文で表現
// while文
for ; sum < 1000; {
    sum += sum
}

// 無限ループ
for {
    // do something
}
  • If文も括弧無しverison。
if x < 0 {
    // do something
}
  • 特殊なのは簡単な式をIf文の中で書ける。スコープはIf文内のみ。
if v := math.Pow(x, n); v < lim {
    return v
}
  • Switch文はcaseで強制break。続けるときは"fallthrough"をcaseの最後に書く。
  • Deferを使うと呼び出し元の関数がreturnする時まで評価を遅らせる。(引数は評価されるがその関数は評価されない)
  • 複数deferがあるときはLIFOの順で処理される。

More types: structs, slices, and maps.

  • ポインタが使える。&でポインタを取得、*でポインタが示す先の値を取得。
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
}
  • 構造体もある。"type 構造体名 struct"の形で宣言。
  • 構造体へはドットでアクセス。
type Vertex struct {
	X int
	Y int
}
func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}
  • ポインタを用いて構造体へアクセスもできる。このとき(*p).Xと書かなくても勝手に解釈してくれる。
func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}
  • 配列は"var 変数名 [サイズ]型"で宣言。配列の長さは後から変えれない。
var a [10]int
  • 配列を便利に扱うためにsliceがある。
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
// [3 5 7]
  • sliceは元の配列の部分を参照しているだけ。したがって、sliceの値を変えると元の配列の値も変わる。
  • sliceを重ねて新たなsliceを作ることができる。
s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4]
fmt.Println(s) // [3 5 7]
s = s[:2]
fmt.Println(s) // [3 5]
s = s[1:]
fmt.Println(s) // [5]
  • sliceはlengthをcapacityを持っている。lengthはsliceに含まれる要素数。capacityはsliceの最初の要素から数えて元の配列の最後の要素までの数。
  • lengthとcapacityはlen(s)とcap(s)の形で取得可能。
  • make関数を用いてsliceを作成。make関数はarrayを作成しそのsliceを返す。
a := make([]int, 5) // len(a)=5
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
  • append関数を用いてsliceに要素を追加することができる。capacityを超えたとき、より大きなサイズの配列を割り当て直す。
var s []int
s = append(s, 0)
s = append(s, 1)
  • rangeを使えば反復処理ができる。rangeを使うと2つの値が返され、1つ目がindex、2つ目がその要素の値。
  • 使わない時は"_"で捨てることができる。
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
}
for _, value := range pow {
    fmt.Printf("%d\n", value)
}
  • Mapを用いてキーと値を関連づけることができる。
  • make関数を用いてmapを作成することで要素の追加などが可能。
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
	40.68433, -74.39967,
}
  • "elem, ok = m[key]"の形でキーに対する要素が存在するかを確認できる。キーがあれば"ok = true"、なければ"ok = false"になる。
m := make(map[string]int)

m["Answer"] = 42 // 初期化
fmt.Println("The value:", m["Answer"])

m["Answer"] = 48 // 更新
fmt.Println("The value:", m["Answer"])

delete(m, "Answer") // 削除
fmt.Println("The value:", m["Answer"])

v, ok := m["Answer"] // 取得
fmt.Println("The value:", v, "Present?", ok)
  • 無名関数を渡すことも可能。
hypot := func(x, y float64) float64 {
	return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))

Methods and interfaces

  • Goにクラスはないが、レシーバ引数を用いてっぽいことができる。レシーバ引数を使う時は"func (変数名 型) 関数名 (引数名 型) 返り値の型 "の形で使う。
  • レシーバ引数を使うとドットを用いてアクセス可能。
  • レシーバを伴うメソッドは同じパッケージ内でする必要がある。
type Vertex struct {
	X, Y float64
}

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

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}
  • ポインタレシーバを用いて関数を宣言することも可能。レシーバの値を更新する時はこちらで。
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
  • ポインタレシーバの時は値でもポインタでもどちらを渡してもうまくやってくれる。
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK
  • インターフェースの宣言もできる。明示的に書かなくても実装すればok。
type I interface {
	M()
}
type T struct {
	S string
}
func (t T) M() {
	fmt.Println(t.S)
}
  • nilをレシーバーにして呼び出すときがあり得るのでnilチェックはするべき
func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}
  • nilインターフェースの値は値も具体的な型ももたない。メソッドを呼び出すとランタイムエラーになる。
type I interface {
	M()
}

func main() {
	var i I
	describe(i)
	i.M() // ランタイムエラー
}
  • アサーションができる。"t, ok := i.(T)"の形で、iがTを保持してればtにその値が入り、okにtrueが入る。otherwise, t=ゼロ値でok=false。
var i interface{} = "hello"

s, ok := i.(string)
fmt.Println(s, ok)
switch v := i.(type) {
case int:
	fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
	fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
	fmt.Printf("I don't know about type %T!\n", v)
}
  • Goではエラーをerror変数を用いて表す。エラー型は以下のインターフェース。
type error interface {
    Error() string
}

Concurrency

  • goroutineという並列化機構がある。"go f(x, y, z)"と書けば別スレッドで動かせる。
  • チャネル型を用いて値のやりとりができる。
ch := make(chan int) // チャネルの作成
c <- sum // 値の送信
x := <-c // 値の受信
  • チャネルはバッファとして使える
ch := make(chan int, 100) // 2つ目の引数でバッファのサイズを指定
  • これ以上値を送信しないことを示すためにチャネルのcloseができる。受信側は2つ目の引数でcloseされているかを知ることができる。
close(ch) // チャネルを閉じる
v, ok := <-ch // チャネルが閉じていればok=false
  • selectを用いて複数のチャネルを待つことができる。defaultを用いれば待ち状態にはならずdefaultを実行する。
select {
case <-tick:
	fmt.Println("tick.")
case <-boom:
	fmt.Println("BOOM!")
	return
default:
	fmt.Println("    .")
        time.Sleep(50 * time.Millisecond)
}
  • syncパッケージを用いて排他制御などをおこなうことができる。
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

func (c *SafeCounter) Inc(key string) {
	c.mux.Lock()
	c.v[key]++
	c.mux.Unlock()
}

Execises

  • Loops and Functions
package main

import (
	"fmt"
	"math"

)

func Sqrt(x float64) float64 {
	z := 1.0
	for i := 0; ; i++ {
		diff := ( z * z - x ) / ( 2 * z )
		if math.Abs(diff) < 1e-10 {
			fmt.Printf("loop count is %d\n", i)
			return z
		} 
		z -= diff
	}
}

func main() {
	fmt.Println(Sqrt(2))
}
  • Slices
package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	pic := make([][]uint8, dy)
	for i := range pic {
		pic[i] = make([]uint8, dx)
	}
	
	for x := 0; x < dx; x++ {
		for y := 0; y < dy; y++ {
			pic[x][y] = uint8(x^y)
		}
	}
	
	return pic
}

func main() {
	pic.Show(Pic)
}
  • Maps
package main

import (
	"golang.org/x/tour/wc"
	"strings"
)

func WordCount(s string) map[string]int {
	m := make(map[string]int)
	for i := range strings.Fields(s) {
		m[strings.Fields(s)[i]] += 1
	}
	return m
}

func main() {
	wc.Test(WordCount)
}
  • Fibonacci closure
package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	n1, n2 := 0, 1
	return func() int {
		n1, n2 = n2, n1+n2
		return n1
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}
  • Stringers
package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
	return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
}
  • Errors
package main

import (
	"fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", float64(e))
}

func Sqrt(x float64) (float64, error) {
	if x < 0.0 {
		return 0.0, ErrNegativeSqrt(x)
	}
	z := 1.0
	for i := 0; ; i++ {
		if z2 := z - (z*z-x)/(2*z); z2 - z < 1e-10 {
			return z, nil
		} else {
			z = z2
		}
	}
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}
  • Readers
package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (r MyReader) Read(b []byte) (int, error) {
	b[0] = 'A'
	return 1, nil
}


func main() {
	reader.Validate(MyReader{})
}
  • rot13Reader
package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func (reader *rot13Reader) Read(b []byte) (n int, err error) {
    n, err = reader.r.Read(b) // bに文字を読み込む
	// 以下で文字を変換する
    for i := range b {
        if ('A' <= b[i] && b[i] <= 'M') || ('a' <= b[i] && b[i] <= 'm') {
            b[i] += 13
        } else if ('N' <= b[i] && b[i] <= 'Z') || ('n' <= b[i] && b[i] <= 'z') {
            b[i] -= 13
        }
    }
    return n, err
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}
  • Images
package main

import (
	"golang.org/x/tour/pic"
	"image"
	"image/color"
)

type Image struct{
	w, h int
}

func (i Image) ColorModel() color.Model {
	return color.RGBAModel 
}

func (i Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, i.w, i.h)
}

func (i Image) At(x, y int) color.Color {
	return color.RGBA{uint8(x*y), uint8(x+y), 255, 255}
}

func main() {
	m := Image{100, 100}
	pic.ShowImage(m)
}
  • Equivalent Binary Trees
package main

import (
	"fmt"
	"golang.org/x/tour/tree"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
	if t == nil {
		return
	}
	Walk(t.Left, ch)
	ch <- t.Value
	Walk(t.Right, ch)
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
	c1, c2 := make(chan int), make(chan int)
    go func() {
        Walk(t1, c1)
        close(c1)
    }()
    go func() {
        Walk(t2, c2)
        close(c2)
    }()
	for {
        x, ok1 := <-c1
        y, ok2 := <-c2
		if !ok1 || !ok2 {
			return true
		}
        if x != y {
            return false
        }
    }
    return true
}

func main() {
	ch := make(chan int)
	t := tree.New(1)
	go func() {
		Walk(t, ch)
		close(ch)
	}()
	for v := range ch {
		fmt.Println(v)
	}
	
	if Same(tree.New(1), tree.New(1)) {
		fmt.Println("Same")
	}
	if !Same(tree.New(1), tree.New(2)) {
		fmt.Println("Not Same")
	}
}
  • Web Crawler
package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch returns the body of URL and
	// a slice of URLs found on that page.
	Fetch(url string) (body string, urls []string, err error)
}

func Crawl(url string, depth int, fetcher Fetcher) {
	m := make(map[string]bool)
	crawl(url, depth, fetcher, m)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func crawl(url string, depth int, fetcher Fetcher, m map[string]bool) {
	if depth <= 0 {
		return
	}
	mux := &sync.Mutex{}
	mux.Lock()
	if v, ok := m[url]; v && ok {
		return
	}
	m[url] = true
	mux.Unlock()
	
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	
	wg := &sync.WaitGroup{}
	for _, u := range urls {
		wg.Add(1)
		go func (u string) {
			crawl(u, depth-1, fetcher, m)
			wg.Done()
		}(u)
	}
	wg.Wait() 
	return
}

func main() {
	Crawl("http://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
	"http://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"http://golang.org/pkg/",
			"http://golang.org/cmd/",
		},
	},
	"http://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"http://golang.org/",
			"http://golang.org/cmd/",
			"http://golang.org/pkg/fmt/",
			"http://golang.org/pkg/os/",
		},
	},
	"http://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
	"http://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
}