💻

A Tour of Go で学んだことまとめ

2023/04/02に公開

1. 概要

Go言語が利用されているプロダクトが増加傾向のようなので公式チュートリアルでもある「A Tour of Go」である程度学んでみたいと思います。そしてちょっと振り返りたいときにこれを自分で参考にします。

https://go-tour-jp.appspot.com/

Welcome!

2. Welcome!

Tourの使い方を学びます。

2.1. Wello World

とりあえず実行してみなさいと言われたので実行しました。

hello.go
package main

import "fmt"

func main() {
	fmt.Println("Hello, 世界")
}
Run
Hello, 世界

日本語訳版なので「Hello, World」ではなく「Hello, 世界」となっていますが、それは気にする問題ではありません。

2.2. Go local

Go Tourは英語以外にもドイツ語や中国語、韓国語等、さまざまな言語で利用できるようです。

2.3. Go offline

今回私はブラウザ上でコードを実行しますが、ローカルで実行する方法が記載されていました。

Goのダウンロードとインストールをして、go tool tourと実行することでローカルでの実行が可能になるようです。

2.4. The Go Playground

ブラウザ上で実行する時は常に2009-11-10 23:00:00 UTCらしいです。これで同一の実行結果を得ることが容易になるんですね。実行結果にも現れています。
常に最新の安定バージョンを利用できるそうなので何も考えずブラウザ上で実行すれば良さそうです。

sandbox.go
package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Welcome to the playground!")

	fmt.Println("The time is", time.Now())
}
Run
Welcome to the playground!
The time is 2009-11-10 23:00:00 +0000 UTC m=+0.000000001

2.5. まとめ

  • Go PlaygroundというWebサービスのサンドボックス内でコンパイル、リンク、実行している
  • 常に2009-11-10 23:00:00 UTCである
Packages, variables, and functions.

3. Packages, variables, and functions.

Goプログラムの基本的なコンポーネントを学びます。

3.1. Packages

Goはpackage(パッケージ)で構成されており、このプログラムはmainパッケージです。
今回はfmtmath/randの二つのパッケージをimport(インポート)しています。

packages.go
package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println("My favorite number is", rand.Intn(10))
}
Run
My favorite number is 7

Go Playgroundでは時刻が一定なのでランダムに生成するrandを用いても出力結果は一定(7)となります。

3.2. Imports

括弧でパッケージのインポートをグループ化しており、公式にもこの記法(factored import statement)が推奨されていますが、以下のように複数のimport文を書くこともできます。

import "fmt"
import "math"

imports.go
package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}
Run
Now you have 2.6457513110645907 problems.

3.3. Exported names

一文字目が大文字で始まる名前は外部のパッケージから参照された名前(exported name)です。
反対に小文字で始まる名前は外部のパッケージから参照することができません。

そこで以下のプログラムを実行してみます。

exported-names.go
package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Println(math.pi)
}
Run
./prog.go:9:19: undefined: math.pi

エラーが出てしまいました。「math.piは定義されてないよ」と言われています。小文字から始まる名前は外部パッケージから参照できませんので、math.Piに変更して実行してみます。

exported-names.go
package main

import (
	"fmt"
	"math"
)

func main() {
-	fmt.Println(math.pi)
+	fmt.Println(math.Pi)
}
Run
3.141592653589793

無事に円周率が表示されました。

3.4. Functions

今回のadd関数はint型の2つの引数を取りますが、変数名の後ろに型名を記述します。

functions.go
package main

import "fmt"

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

func main() {
	fmt.Println(add(42, 13))
}
Run
55

42+13の結果が出力されています。

3.5. Functions continued

複数の引数の型が同一の時、最後だけ残して省略することができます。例えば以下のようにします。

functions-continued.go
package main

import "fmt"

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

func main() {
	fmt.Println(add(42, 13))
}
Run
55

3.6. Multiple results

複数の戻り値を返すこともできます。
以下のswap関数を見てみます。

multiple-result.go
package main

import "fmt"

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

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

このように、a,bそれぞれに代入できています。

3.7. Named return values

戻り値となる変数に名前をつけることができます。こうすることで、returnのみで値を返すことができます。('naked return')
長い関数で利用すると読みやすさに悪影響を与えるため、短い関数でのみ利用するべきです。

named-result.go
package main

import "fmt"

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

func main() {
	fmt.Println(split(17))
}
Run
7 10

3.8. Variables

var文は変数を宣言することができます。これも関数の引数と同様に変数名の最後に型名を書くことで省略することができます。

variables.go
package main

import "fmt"

var c, python, java bool

func main() {
	var i int
	fmt.Println(i, c, python, java)
}
Run
0 false false false

ご覧の通り、関数内でもvar文は利用可能です。

3.9. Variables with initializers

varで変数を宣言する際に初期化子を与えることができます。その際、型を省略することができるようになります。

variables-with-initializers.go
package main

import "fmt"

var i, j int = 1, 2

func main() {
	var c, python, java = true, false, "no!"
	fmt.Println(i, j, c, python, java)
}
Run
1 2 true false no!

main関数内では型宣言をしていませんが、初期化子を与えることで動作させることができています。

3.10. Short variable declarations

関数の中でのみvarの代わりに:=の暗黙的な型宣言が可能です。

package main

import "fmt"

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

	fmt.Println(i, j, k, c, python, java)
}
Run
1 2 3 true false no!

3.11. Basic types

基本型は以下の通りです。

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名
     // Unicode のコードポイントを表す

float32 float64

complex64 complex128
basec-types.go
package main

import (
	"fmt"
	"math/cmplx"
)

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

func main() {
	fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
	fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
	fmt.Printf("Type: %T Value: %v\n", z, z)
}
Run
Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)

3.12. Zero values

変数に初期値を与えない場合はゼロ値が代入されます。

整数型 0
bool型 false
string型 ""
zero.go
package main

import "fmt"

func main() {
	var i int
	var f float64
	var b bool
	var s string
	fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
Run
0 0 false ""

3.13. Type conversions

変数v、型Tがあった時、型変換はT(v)で行います。

type-conversions.go
package main

import (
	"fmt"
	"math"
)

func main() {
	var x, y int = 3, 4
	var f float64 = math.Sqrt(float64(x*x + y*y))
	var z uint = uint(f)
	fmt.Println(x, y, z)
}
Run
3 4 5

以下のように省略して記述することも可能です。

type-conversions.go
package main

import (
	"fmt"
	"math"
)

func main() {
-	var x, y int = 3, 4
-	var f float64 = math.Sqrt(float64(x*x + y*y))
-	var z uint = uint(f)
+	x, y  := 3, 4
+	f := math.Sqrt(float64(x*x + y*y))
+	z := uint(f)
	fmt.Println(x, y, z)
}
Run
3 4 5

3.14. Type inference

:=var =等で明示的な型を指定しない場合、右側の変数から型推論されます。
型を指定しない数値である場合、右側の定数の精度に基づいてint,float64,complex128になります。

type-inference.go
package main

import "fmt"

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

change me!と言われているため3.14に変更してみます。

type-inference.go
package main

import "fmt"

func main() {
-	v := 42 // change me!
+	v := 3.14 // change me!
	fmt.Printf("v is of type %T\n", v)
}
Run
v is of type float64

intからfloat64に変わりました。

3.15. Constants

定数はconstを用いて宣言します。
ただし、:=を利用することはできません。

constants.go
package main

import "fmt"

const Pi = 3.14

func main() {
	const World = "世界"
	fmt.Println("Hello", World)
	fmt.Println("Happy", Pi, "Day")

	const Truth = true
	fmt.Println("Go rules?", Truth)
}
Run
Hello 世界
Happy 3.14 Day
Go rules? true

3.16. Numeric Constants

型のない定数は状況により必要な型を取ることになります。
以下のプログラムでは、Big,Smallに型が宣言されていないため計算結果により型が変わっています。

numeric-constants.go
package main

import "fmt"

const (
	// Create a huge number by shifting a 1 bit left 100 places.
	// In other words, the binary number that is 1 followed by 100 zeroes.
	Big = 1 << 100
	// Shift it right again 99 places, so we end up with 1<<1, or 2.
	Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
	return x * 0.1
}

func main() {
	fmt.Println(needInt(Small))
	fmt.Println(needFloat(Small))
	fmt.Println(needFloat(Big))
}
Run
21
0.2
1.2676506002282295e+29

ちなみにneedInt(Big)とすると値が大きくなりすぎてオーバーフローしエラーが出力されました。

3.17. まとめ

  • Goのプログラムはpackage(パッケージ)で構成される
  • エントリーポイントはmain関数
  • 大文字から始まる名前だけ外部パッケージから参照することができる
  • 型名は変数の後ろに書く
  • 同じ型の引数が複数ある場合型の宣言は最後だけ残して省略できる
  • 戻り値となる変数に名前をつけることでreturn文に何も書かなくても良くなる
  • var文で変数を宣言し、引数同様型宣言の省略も可能
  • 初期化子を与えることで型宣言を省略可能
  • 関数の中でのみvarの代わりに:=の暗黙的な型宣言が可能です
  • 変数に初期値を与えない場合、ゼロ値が代入される
  • 変数v、型Tがあった時、型変換はT(v)で行う
  • :=var =等で明示的な型を指定しない場合、右側の変数から型推論される
  • 定数はconstを用いて宣言できるが、:=を利用することはできない。
  • 型のない定数は状況により必要な型を取ることになる。
Flow control statements: for, if, else, switch and defer

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

条件文とループ、switch,deferを使ってコードの流れをコントロールする方法を学びます。

4.1. For

for文は、初期化・条件・後処理の3つの処理がセミコロン;で分けられています。
CやJava等の言語との違いとして、それらを囲う丸括弧()が必要ないということです。

for.go
package main

import "fmt"

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

4.2. For continued

初期化と後処理は任意です。
不要な場合は;のみ記述します。

for-continued.go
package main

import "fmt"

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

4.3. For is Go's "while"

なんなら、;を省略することだってできてしまいます。つまりC言語等のwhile文はGo言語におけるfor文であるのです。

for-is-gos-while.go
package main

import "fmt"

func main() {
	sum := 1
-	for ; sum < 1000; {
+	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}
Run
1024

4.4. Forever

条件式を省略すると無限ループとなります。

forever.go
package main

func main() {
	for {
	}
}
Run

4.5. If

if文にはfor文同様に丸括弧()が必要ありません。

if.go
package main

import (
	"fmt"
	"math"
)

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

func main() {
	fmt.Println(sqrt(2), sqrt(-4))
}
Run
1.4142135623730951 2i

4.6. If with a short statement

for文のように条件式の前に簡単な文を書くことができます。

if-with-a-short-statement
package main

import (
	"fmt"
	"math"
)

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

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}
Run
9 20

4.7. If and else

if文で宣言された変数はelse文でも用いることができます。

if-and-else.go
package main

import (
	"fmt"
	"math"
)

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
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}
Run
27 >= 20
9 20

なお、main関数のPrintlnは先にpow関数が実行されてから実行されるため、pow関数のPrintfが先に実行され、その後9 20が出力されています。

4.8. Exercise: Loops and Functions

関数とループを利用して平方根を計算してみます。数値xが与えられた時に幾つかのzを推測し、z^2がどれだけxに近づいたかに応じてzを調節できます。

exercise-loops-and-functions.go
package main

import (
	"fmt"
)

func Sqrt(x float64) float64 {
}

func main() {
	fmt.Println(Sqrt(2))
}

4.8.1. 解答例

mathパッケージをインポートして出力を比較してみました。

exercise-loops-and-functions.go
package main

import (
	"fmt"
+	"math"
)

func Sqrt(x float64) float64 {
+	z := 1.0
+	for i := 0; i < 10; i++ {
+		z -= (z*z - x) / (2*z)
+	}
+	return z
}

func main() {
	fmt.Println(Sqrt(2))
+	fmt.Println(math.Sqrt(2))
}

4.9. Switch

switch文はif-else文を短く書くことができます。

他言語と比較してGo言語は、選択されたcaseのみ実行しその他のcaseは実行されません。また、最後に必要なbreakも自動で提供されるため必要ありません。
さらに、case定数でも整数でもなくて良いのです。

switch.go
package main

import (
	"fmt"
	"runtime"
)

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.\n", os)
	}
}
Run
Go runs on Linux.

4.10. Switch evaluation order

case文は上から下に評価されるため、条件が一致するとそこでswitch文は終了(自動的にbreak)します。

switch-evaluation-order.go
package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("When's Saturday?")
	today := time.Now().Weekday()
	switch time.Saturday {
	case today + 0:
		fmt.Println("Today.")
	case today + 1:
		fmt.Println("Tomorrow.")
	case today + 2:
		fmt.Println("In two days.")
	default:
		fmt.Println("Too far away.")
	}
}
Run
When's Saturday?
Too far away.

この実行結果を見るに、火曜日から金曜日の間であるようです。果たして2009-11-10 23:00:00 UTCは何曜日なのでしょうか。

4.11. Switch with no condition

条件のないswitch文は、switch trueと書くことと同様です。これを利用して、長くなりがちなif-then-elseをシンプルに表現することが可能となります。

switch-with-no-condition.go
package main

import (
	"fmt"
	"time"
)

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.")
	}
}
Run
Good evening.

2009-11-10 23:00:00 UTCは23時なのでGood evening.が出力されています。

4.12. Defer

defer文は元の関数の実行をreturnするまで遅延させます。関数の引数はすぐに評価されますが、実行そのものはreturnされるまで実行されません。

defer.go
package main

import "fmt"

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

	fmt.Println("hello")
}
Run
hello
world

4.13. Stacking defers

deferを複数回利用した場合、それらはスタックされます。元の関数がreturnされると、 LIFO(last-in-first-out)の順で実行されます。

defer-multi.go
package main

import "fmt"

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

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

	fmt.Println("done")
}
Run
counting
done
9
8
7
6
5
4
3
2
1
0

4.14. まとめ

  • for文には()が必要ない
  • 初期化と後処理を省略し、他言語のwhile文同様に扱うことが可能
  • 条件式を省略すると無限ループする
  • if文にも()は必要ない
  • 条件式の前に簡単な文を記述可能
  • そこで得た変数はelse文でも利用可能
  • switch文にはbreakが必要ない
  • case文は定数でも整数でもなくて良い
  • case文は上から順に評価され、一致するとそこで終了する
  • 条件のないswitch文はif-then-elseをシンプルに表現可能
  • defer文は元の関数の実行をreturnするまで遅延させる
  • 複数回利用した場合、それらはスタックされ、LIFOで実行される
More types: structs, slices, and maps.

5. More types: structs, slices, and maps.

既存の型に基づいて新しい型を定義する方法を学びます。
構造体、配列、slice、mapについて。

5.1. Pointers

変数Tのポインタは、*T型で、ゼロ値はnilです。

ポインタ演算はありません。

主な仕様は以下のプログラムを見ればわかりますが、&でアドレスを引き出し、*でそのアドレスが指す変数を表します。

pointers.go
package main

import "fmt"

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
}
Run
42
21
73

5.2. Structs

struct(構造体)は、field(フィールド)の集まりです。

structs.go
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

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

5.3. Struct Fields

field.を用いてアクセスします。

struct-fields.go
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

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

5.4. Pointers to structs

structfieldはポインタを用いてアクセスすることもできます。

struct-pointers.go
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}
Run
{1000000000 2}

5.5. Struct Literals

:で特定の値だけ初期化することができます。また、先頭に&をつけると新たなstructへのポインタが代入されます。

strut-literals.go
package main

import "fmt"

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)
}
Run
{1 2} &{1 2} {1 0} {0 0}

v2ではXのみ初期化しているのでYはゼロ値(0)となりました。

5.6. Arrays

[n]T型は、型Tn個の変数のarray(配列)を表します。

例えばint型の10個の配列を作るには次のようにします。
var n [10]int
配列の長さは型の一部なので変更することはできません。

array.go
package main

import "fmt"

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)
}
Run
Hello World
[Hello World]
[2 3 5 7 11 13]

配列そのものを出力する際は配列ということがわかるように[]がついています。

5.7. Slices

[]Tは型Tslice(スライス)を表します。
配列は固定長ですがスライスは可変長です。

slices.go
package main

import "fmt"

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

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

このプログラムの場合、primesの要素1から3が選択されています。Pythonと同じような記法みたいです。

5.8. Slices are like references to arrays

slice配列へのポインタ(参照)です。つまりスライスの要素を変更すると配列の要素も変更されますし、同一の要素を共有しているスライスも変更されます。

slice-pointers.go
package main

import "fmt"

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

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}
Run
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]

これを利用すると特定のユーザー名が変更された時一箇所変更を加えるだけで全体を変更できるので便利そうです。

5.9. Slice literals

[3]bool{true, false, false}これは配列リテラルですが、
[]bool{true, false, false}これは上記と同様の配列を作成し、それを参照するスライスを作成します。

slice-literals.go
package main

import "fmt"

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)
}
Run
[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]

5.10. Slice defaults

次の配列においてvar n [10]int
n[0:10] n[:10] n[0:] n[:]これらは等価です。
これもPythonにあります。

slice-bounds.go
package main

import "fmt"

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

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}
Run
[3 5 7]
[3 5]
[5]

5.11. Slice length and capacity

スライスはlength(長さ)とcapacity(容量)の両方を持ちます。長さは要素の数で、容量はスライスの最初の要素から数えて元の配列の要素数です。
スライスsの長さはlen(s)容量はcap(s)という関数で求められます。

slice-len-cap.go
package main

import "fmt"

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

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
Run
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]

それではスライスを拡張し、容量を超えてしまった場合はどうなるのでしょうか。

slice-len-cap.go
package main

import "fmt"

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

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
	
+	s = append(s, 1, 2, 3)
+	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
Run
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
+len=5 cap=8 [5 7 1 2 3]

長さは書いてあるとおり拡張されましたが、なんと容量は2倍に増加しています。これはGo言語の仕様で、スライスが拡張された場合は基本的に容量が2倍確保されるようです。
これはスライスの容量が超過する毎に新たな領域を確保しているため、1ずつ増やしていては効率が悪いためです。

5.12. Nil slices

2回目ですが、スライスのゼロ値はnilです。
長さも容量も共に0で、当然ですが元となる配列もありません。

nil-slices.go
package main

import "fmt"

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

5.13. Creating a slice with make

make関数を使用することで、ゼロ化された配列を割り当て、それを指すスライスを返します。
第二引数に長さ、第三引数に容量を指定することができます。

making-slices.go
package main

import "fmt"

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)
}

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}
Run
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]

5.14. Slices of slices

スライスの中にはスライスを含め他の型を入れることができます。

slices-of-slice.go
package main

import (
	"fmt"
	"strings"
)

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], " "))
	}
}
Run
X _ X
O _ X
_ _ O

5.15. Appending to a slice

先ほども利用しましたが、スライスに新たな要素を追加するときは、append関数を利用します。
要素を追加した際に容量をオーバーしてしまったときは新たに配列を割り当て直しスライスはそこを指すようになります。

append.go
package main

import "fmt"

func main() {
	var s []int
	printSlice(s)

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s)

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s)

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
Run
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]

5.16. Range

rangeは配列やスライスを反復処理する際に利用し、二つの値を返します。
例えばスライスの場合、一つはindex、もう一つはそのインデックスの要素のコピーです。

range.go
package main

import "fmt"

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)
	}
}
Run
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128

5.17. Range continued

インデックスや値は_で捨てることができます。
また、二つ目の変数を省略することでインデックスだけを取り出すこともできます。

range-continued.go
package main

import "fmt"

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)
	}
}
Run
1
2
4
8
16
32
64
128
256
512

5.18. Exercise: Slices

Pic関数を実装してみます。
長さdyのスライスに各要素が八ビットのuint型で長さdxのスライスを割り当てたものを返す必要があります。実行すると画像が表示されます。
使用する関数の例として以下のようなものがおすすめされていました。
(x+y)/2x*yx^y

exercise-slices.go
package main

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

func Pic(dx, dy int) [][]uint8 {
}

func main() {
	pic.Show(Pic)
}

5.18.1. 解答例

exercise-slices.go
package main

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

func Pic(dx, dy int) [][]uint8 {
+	ss := make([][]uint8, dy)
+		for y := 0; y < dy; y++ {
+			s := make([]uint8, dx)
+			for x := 0; x < dx; x++ {
+				s[x] = uint8((x + y) / 2)
+			}
+			ss[y] = s
+		}
+	return ss
}

func main() {
	pic.Show(Pic)
}

Run

5.19. Maps

mapはキーと値を関連付け、ゼロ値はnilで、キーを持たず追加することもできません。
make関数は指定された型のマップを初期化し、使用可能な状態で返します。

maps.go
package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"])
}
Run
{40.68433 -74.39967}

5.20. Map literals

mapリテラルはstructリテラルと似ていますが、key(キー)が必要です。

map-literals.go
package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

func main() {
	fmt.Println(m)
}
Run
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]

5.21. Map literals continued

mapに渡すトップレベルの型が単純な型名の場合、リテラルの要素から推定できるため省略が可能です。

map-literals-continued
package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}
Run
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]

5.22. Mutating Maps

mapのキーに対する要素が存在する場合は二つ目の要素(boolean)で判断し、存在すればtrue存在しなければfalseで確認できます。

mutating-maps.go
package main

import "fmt"

func main() {
	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)
}
Run
The value: 42
The value: 48
The value: 0
The value: 0 Present? false

5.23. Exercise: Maps

今回はWordCount関数を実装します。
stringで渡される文章の各単語の出現回数のmapを返します。
wc.Test関数はテストスイートを実行し成功か否かを出力します。

exercise-maps.go
package main

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

func WordCount(s string) map[string]int {
	return map[string]int{"x": 1}
}

func main() {
	wc.Test(WordCount)
}

5.23.1. 解答例

exercise-maps.go
package main

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

func WordCount(s string) map[string]int {
+	m := make(map[string]int)
+	str := strings.Fields(s)
+	for _, v := range str {
+		m[v]++
+	}
+	return m
- return map[string]int{"x": 1}
}

func main() {
	wc.Test(WordCount)
}
Run
PASS
 f("I am learning Go!") = 
  map[string]int{"Go!":1, "I":1, "am":1, "learning":1}
PASS
 f("The quick brown fox jumped over the lazy dog.") = 
  map[string]int{"The":1, "brown":1, "dog.":1, "fox":1, "jumped":1, "lazy":1, "over":1, "quick":1, "the":1}
PASS
 f("I ate a donut. Then I ate another donut.") = 
  map[string]int{"I":2, "Then":1, "a":1, "another":1, "ate":2, "donut.":2}
PASS
 f("A man a plan a canal panama.") = 
  map[string]int{"A":1, "a":2, "canal":1, "man":1, "panama.":1, "plan":1}

5.24. Function values

関数も変数の一種ですので、関数に関数を渡したり、関数値は関数の引数に取ることも、戻り値としても利用できます。

とてもややこしいですね。

function-values.go
package main

import (
	"fmt"
	"math"
)

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))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}
Run
13
5
81

5.25. Function closures

Golangの関数はclosure(クロージャ)で、関数内で定義された関数であり、外部変数を参照することができます。これにより状態を保持し、カプセル化出来ます。Javaとかやったことある人はわかりやすいかもしれません。

function-closures.go
package main

import "fmt"

func adder() func(int) int {
	sum := 0
	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),
		)
	}
}
Run
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90

5.26. Exercise: Fibonacci closure

フィボナッチ関数を実装します。
この関数は連続するフィボナッチ数を返す関数(クロージャ)を返します。

exercise-fibonacci-closure.go
package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
}

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

5.26.1. 解答例

exercise-fibonacci-closure.go
package main

import "fmt"

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

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}
Run
21
​
1
1
2
3
5
8
13
21
34
55

5.27. まとめ

  • ポインタのゼロ値はnil
  • &でアドレス、*で変数を表す
  • ポインタ演算は無い
  • structfieldの集まり
  • field.を用いてアクセスする
  • :で特定の値だけ初期化することができる
  • 先頭に&をつけると新たなstructへのポインタが代入される
  • [n]T型は、型Tn個の変数のarrayを表す
  • 配列の長さを変えることはできない
  • []Tは型Tsliceを表す
  • スライスは可変長
  • slice配列へのポインタにすぎない
  • スライスsの長さはlen(s)容量はcap(s)という関数を利用する
  • make関数でスライスを返し、長さと容量を指定できる
  • スライスの中にはスライスを含め他の型を入れることができる
  • スライスに要素を追加するときはappend関数を利用する
  • rangeで配列やスライスを反復処理可能
  • 一つはindex、もう一つは要素のコピーを返す
  • _に代入することで値を切り捨てることが可能
  • mapはキーと値を関連付ける
  • make関数で指定された型のマップを初期化し、使用可能な状態で返す
  • mapリテラルはkeyが必要
  • mapに渡す型名が単純な型名の場合省略可能
  • キーに対する要素が存在するかは二つ目の要素(boolean)で判断
  • 関数も変数の一種
  • Goの関数はclosureで、カプセル化が可能となっている
Methods and interfaces

6. Methods and interfaces

メソッドとインターフェース、オブジェクトとその動作を定義する構造体について学びます。

6.1. Methods

Go言語では、型にmethod(メソッド)を定義することができます。
以下の例では、Absメソッドはvという名を持つVertex型のレシーバを持つことを意味しています。

methods.go
package main

import (
	"fmt"
	"math"
)

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())
}
Run
5

6.2. Methods are functions

今回のAbsは先ほどと同様の機能を維持したまま、通常の関数として記述しています。

methods-funcs.go
package main

import (
	"fmt"
	"math"
)

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))
}
Run
5

6.3. Methods continued

任意の型にもメソッドを宣言できます。
レシーバを伴うメソッドの宣言には、同一のパッケージ内にレシーバ型が存在する必要があります。

methods-continued.go
package main

import (
	"fmt"
	"math"
)

type MyFloat float64

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

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

6.4. Pointer receivers

ポインタレシーバでメソッドを宣言することもできます。
レシーバ自身を変更したい際はポインタレシーバにします。
これはいわゆる破壊的処理と言われるようなものだと思います。
逆に非破壊的処理を求める場合は前回のような変数レシーバにする必要があります。
今回の場合、Scale関数を変数レシーバ(v Vertex)とするとRun5となります。

methods-pointers.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

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

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

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

6.5. Pointers and functions

以下のコードは先ほどのAbsScaleメソッドを関数に書き直してあります。

methods-pointers-explained.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

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

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

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

6.6. Methods and pointer indirection

メソッドがポインタレシーバの場合、呼び出し時に変数、ポインタどちらでもレシーバとして取ることができます。
v.scale(5)はGoの場合利便性のため(&v).scale(5)として処理されます。

関数の場合は特定の型を取る必要があります。

indirection.go
package main

import "fmt"

type Vertex struct {
	X, Y float64
}

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

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

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

	p := &Vertex{4, 3}
	p.Scale(3)
	ScaleFunc(p, 8)

	fmt.Println(v, p)
}
Run
{60 80} &{96 72}

6.7. Methods and pointer indirection (2)

メソッドが変数レシーバの場合も、変数、ポインタどちらでもレシーバとして取ることができます。

indirection-values.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

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

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

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

	p := &Vertex{4, 3}
	fmt.Println(p.Abs())
	fmt.Println(AbsFunc(*p))
}
Run
5
5
5
5

6.8. Choosing a value or pointer receiver

ポインタレシーバを用いる利点は以下の通りです。

  1. 破壊的処理を実行する
  2. メソッドの呼び出し毎に変数のコピーを行わない
    (2.)に関しては、大きな構造体の際に有効です。
    一般的に、レシーバは値とポインタを混在させるべきではありません。
methods-with-pointer-receivers.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

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

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

func main() {
	v := &Vertex{3, 4}
	fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
	v.Scale(5)
	fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}
Run
Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25

6.9. Interfaces

interface(インターフェース)は、メソッドのシグニチャの集まりで表されます。
そのメソッドの集まりを実装した値をインターフェース型の変数へ持たせることができます。

つまり以下の例でエラーが出るのは、Absメソッドが*Vertexの定義であるため、Vertexであるvaへ代入することはできないためです。

interfaces.go
package main

import (
	"fmt"
	"math"
)

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // a MyFloat implements Abser
	a = &v // a *Vertex implements Abser

	// In the following line, v is a Vertex (not *Vertex)
	// and does NOT implement Abser.
	a = v

	fmt.Println(a.Abs())
}

type MyFloat float64

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

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
Run
./prog.go:22:6: cannot use v (variable of type Vertex) as Abser value in assignment: Vertex does not implement Abser (method Abs has pointer receiver)

6.10. Interfaces are implemented implicitly

型にメソッドを追加してインターフェースを実装します。
またGo言語には、暗黙のインターフェースというものがあり、これは異なるパッケージの、型が同じインターフェースを実装することが出来るようになっています。

interfaces-are-satisfied-implemented.go
package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I = T{"hello"}
	i.M()
}
Run
hello

6.11. Interface values

インターフェースは、値と型のタプルのようにして考えることができます。
(value, type)
値は、特定の基底となる具体的な型の値を保持し、メソッドを呼び出すとその型と同じメソッドが呼び出されます。

なんだかオーバーロードっぽい印象を受けました。

interface-values.go
package main

import (
	"fmt"
	"math"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	fmt.Println(t.S)
}

type F float64

func (f F) M() {
	fmt.Println(f)
}

func main() {
	var i I

	i = &T{"Hello"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
Run
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793

6.12. Interface values with nil underlying values

値がnilの時、Go言語ではヌルポにはならずnilをレシーバとして実行されます。nilで呼び出されても適切に処理するよう記述しておくのが一般的とされています。

interface-values-with-nil.go
package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
	var i I

	var t *T
	i = t
	describe(i)
	i.M()

	i = &T{"hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
Run
(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello

6.13. Nil interface values

nilインターフェースには具体的な値も型も存在しません。よってメソッドを呼び出すとエラーとなります。

nil-interface-values.go
package main

import "fmt"

type I interface {
	M()
}

func main() {
	var i I
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
Run
(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x482501]

goroutine 1 [running]:
main.main()
	/tmp/sandbox1367158980/prog.go:12 +0x61

6.14. The empty interface

0個のメソッドが指定されたインターフェースは、空のインターフェースと呼ばれ、任意の値の型を保持できます。
未知の型を扱う際に利用され、例えばfmt.Printではinterface{}型の任意の引数を受け取ります。

empty-interface.go
package main

import "fmt"

func main() {
	var i interface{}
	describe(i)

	i = 42
	describe(i)

	i = "hello"
	describe(i)
}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}
Run
(<nil>, <nil>)
(42, int)
(hello, string)

6.15. Type assertions

type assertion(型アサーション)は値の基になる具体的な値を利用する手段を提供します。
t := i.(T)
iが型Tを保持しtに代入されます。この時iTを保有していなければpanicを引き起こします。
t, ok := i.(T)
しかし上記のようにすると、okiTを保有しているかのbooleanが返され、保有していればtrueを、そうでなければfalseを返します。また、tにはその値、もしくはゼロ値が代入されます。

type-assertions.go
package main

import "fmt"

func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)

	s, ok := i.(string)
	fmt.Println(s, ok)

	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) // panic
	fmt.Println(f)
}
Run
hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64

goroutine 1 [running]:
main.main()
	/tmp/sandbox3937253924/prog.go:17 +0x14a

6.16. Type switches

type switchは通常のswitch文のようにして使えますが、比較対象が値ではなく型となり、type assersion()内はtypeと書きます。

type-switches.go
package main

import "fmt"

func do(i interface{}) {
	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)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}
Run
Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!

6.17. Stringers

最もよく使われているインターフェースの一つに、stringerというものがあります。
これはfmtパッケージ含まれており、変数を文字列として出力するために使用されます。

stringer.go
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)
}
Run
Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)

6.18. Exercise: Stringers

IPAddr型を実装します。
IPアドレスをドットで四つに区切った表現で出力するために、fmt.Stringerインターフェースを実装します。

(例 : IPAddr{1, 2, 3, 4}は"1.2.3.4")

exercise-stringer.go
package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.

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)
	}
}

6.18.1. 解答例

前回のようにしてPrintする際の出力形式を変更してあげます。
また、ダブルコーテーション("")はバックスラッシュでエスケープします。

exercise-stringer.go
package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.

+func (ip IPAddr) String() string {
+	return fmt.Sprintf("\"%d.%d.%d.%d\"", 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)
	}
}
Run
loopback: "127.0.0.1"
googleDNS: "8.8.8.8"

6.19. Errors

Go言語はエラーの状態をerrorで表します。
error型は以下のような組み込み型のインターフェースです。
type error interface { Error() string }
errorの取り扱いは値がnilか否かで判断します。

errors.go
package main

import (
	"fmt"
	"time"
)

type MyError struct {
	When time.Time
	What string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("at %v, %s",
		e.When, e.What)
}

func run() error {
	return &MyError{
		time.Now(),
		"it didn't work",
	}
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err)
	}
}
Run
at 2009-11-10 23:00:00 +0000 UTC m=+0.000000001, it didn't work

6.20. Exercise: Errors

Sqrt関数で負の数が与えられた際にerrorを返すようにします。
以下の新しい型を作成します。
type ErrNegativeSqrt float64
また、ErrNegativeSqrt(-2).Error()で、"cannot Sqrt negative number: -2"を返す
func (e ErrNegativeSqrt) Error() string
メソッドを実装し、errorインタフェースを満たすようにします。

exercise-errors.go
package main

import (
	"fmt"
)

func Sqrt(x float64) (float64, error) {
	return 0, nil
}

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

6.20.1. 解答例

Errorメソッド内でfmt.Sprint(e)とすると無限ループとなります。理由は、Errorメソッドが呼び出されるたびにErrorメソッドが呼び出される(再帰的)ためです。
ここでfmt.Sprint(float(e))とする事でeを文字列に変換し、ループに陥ることを防ぐことができます。

exersise-errors.go
package main

import (
	"fmt"
)

+type ErrNegativeSqrt float64

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

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

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}														
Run
1.414213562373095 <nil>
0 cannot Sqrt negative number: -2

6.21. Readers

ioパッケージはデータストリームを読むio.Readerインターフェースを規定しています。
io.ReaderインターフェースはReadメソッドを持ちます。与えられたバイトスライスへ入れ、バイトのサイズとエラーの値を返します。
終端はio.EOFのエラーを返します。

今回は8byte毎に呼び出しています。

reader.go
package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
}
Run
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""

6.22. Exercise: Readers

アスキー文字Aの無限ストリームを出力するReader型を定義します。

exercise-reader.go
package main

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

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.

func main() {
	reader.Validate(MyReader{})
}

6.22.1. 解答例

range bはインデックスを返すのでfor文で回す事でAを代入することができます。

exercise-reader.go
package main

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

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.

+func (r MyReader) Read(b []byte) (int, error) +{
+	for i := range b {
+		b[i] = 'A'
+	}
+	return len(b), nil
+}

func main() {
	reader.Validate(MyReader{})
}
Run
OK!

6.23. Exercise: rot13Reader

exercise-rot-reader.go
package main

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

type rot13Reader struct {
	r io.Reader
}

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

6.23.1. 解答例

まずエラーの処理を行い、その後rot13の暗号化を行います。

exercise-rot-reader.go
package main

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

type rot13Reader struct {
	r io.Reader
}

+func (rr *rot13Reader) Read(b []byte) (int, error) {
+	n, err := rr.r.Read(b)
+	if err != nil {
+		return n, err
+	}

+	for i := 0; i < n; i++ {
+		if (b[i] >= 'A' && b[i] < 'N') || (b[i] >= 'a' && b[i] < 'n') {
+			b[i] += 13
+		} else if (b[i] > 'M' && b[i] <= 'Z') || (b[i] > 'm' && b[i] <= 'z') {
+			b[i] -= 13
+		}
+	}

+	return n, nil
+}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}
Run
You cracked the code!

6.24. Images

imageパッケージは、次のようなImageインターフェースを実装しています。
type Image interface { ColorModel() color.Model Bounds() Rectangle At(x, y int) color.Color }
Rectangleは、imageパッケージの
image.Rectangleに定義されています。

color.Color,color.Modelはインタフェースですが、定義済みのcolor.RGBA,color.RGBAModelを使用し、無視できます。 これらは、image/colorパッケージに定義されています。

images.go
package main

import (
	"fmt"
	"image"
)

func main() {
	m := image.NewRGBA(image.Rect(0, 0, 100, 100))
	fmt.Println(m.Bounds())
	fmt.Println(m.At(0, 0).RGBA())
}
Run
(0,0)-(100,100)
0 0 0 0

6.25. Exercise: Images

image.Imageインターフェースの実装を返します。
Image型を定義して必要なメソッドを実装し、pic.ShowImageを呼び出します。

Boundsは、image.Rect(0, 0, w, h)のようにしてimage.Rectangleを返します。

ColorModelは、color.RGBAModelを返します。

Atはひとつの色を返します。
生成する画像の色の値vcolor.RGBA{v, v, 255, 255}を利用して返します。

exercise-images.go
package main

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

type Image struct{}

func main() {
	m := Image{}
	pic.ShowImage(m)
}

6.25.1. 解答例

Imageは縦横256ピクセルの画像を表現する構造体で、Boundsメソッドで画像の範囲を定義し、ColorModelメソッドでカラーモデルを定義し、Atメソッドで各ピクセルの色を定義しています。
pic.ShowImage関数を呼び出すことで、このImageの実装に基づいた画像が表示されます。

exercise-images-ans.go
package main

import (
	"image"
	"image/color"

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

type Image struct {
	w, h int
}

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

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

func (i Image) At(x, y int) color.Color {
	v := uint8((x + y) / 2)
	return color.RGBA{v, v, 255, 255}
}

func main() {
	m := Image{256, 256}
	pic.ShowImage(m)
}

Run

6.26. まとめ

  • structを含む任意の型にmethodを定義可能
  • ポインタレシーバで宣言可能(破壊的処理)
  • メソッドは呼び出し時に変数、ポインタどちらでもレシーバとして取ることが可能。
  • 関数は特定の型を取る必要がある
  • レシーバは値かポインタで混在させるべきではない
  • interface(インターフェース)は、メソッドのシグニチャの集まり。
  • そのメソッドの集まりを実装した値をinterface型の変数に持たせることが可能
  • 型にメソッドを追加してインターフェースを実装する
  • 暗黙のインターフェースにより異なるパッケージの、型が同じインターフェースを実装可能
  • インターフェースは値と型のタプルとして考えられる
  • 値の型と同じメソッドが実行される
  • 値がnilでも実行されるため適切な処理を記述しておく
  • nilインターフェースには具体的な値も型も存在しないため、メソッドを呼び出すとエラーとなる
  • 空のインターフェースは任意の値の型を保持する。
  • type assersionはインターフェースの値の基になる具体的な値を利用する手段を提供する
  • type switchは値の型を比較する
  • stringerインターフェースは変数を文字列として出力するために広く利用されている
  • error型も組み込み型のインターフェース
  • nilか否かで判断する
  • ioパッケージはデータストリームを読むio.Readerインターフェースを規定している
  • Readメソッドを持ち、終端はio.EOFのエラーを返す
  • ImageインターフェースはColorModel,Bounds,Atのメソッドを持つ
Concurrency

7. Concurrency

goroutine,channelの概要とそれらを使ってさまざまな並行処理を実装する方法について学びます。

7.1. Goroutines

goroutine(ゴルーチン)はランタイムに管理される軽量なスレッドです。
goというキーワードを使用することで処理を非同期に実行することが可能となります。

goroutines.go
package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}
Run
hello
world
world
hello
hello
world
world
hello
hello

7.2. Channels

channel(チャネル)型は、<-を用いて値の送受信ができます。

マップとスライスのように、チャネルは以下のように生成します
ch := make(chan int)
通常、片方が準備できるまで送受信はブロックされることで、goroutineの同期を可能にします。

2つのgoroutine間で作業を分配し、両方の計算が完了すると、最終結果が計算されます。

channels.go
package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}
Run
-5 17 12

7.3. Buffered Channels

チャネルはBuffer(バッファ)として扱え、makeの第二引数にバッファの長さを与えることで初期化することができます。

buffered-channels.go
package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}
Run
1
2

ではこれをバッファが詰まるように変更してみます。

buffered-channels.go
package main

import "fmt"

func main() {
-	ch := make(chan int, 2)
+	ch := make(chan int, 1)
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}
Run
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/sandbox2141988880/prog.go:8 +0x4b

ちゃんとエラーを吐いてくれました。

7.4. Range and Close

受信の式に二つ目のパラメータを割り当て、送り手のチャネルがcloseされている確認できます。受信する値がなく、チャネルがcloseしているとき値はfalseとなります。
しかし通常はcloseする必要はなく、range等でこれ以上情報がないことを知る必要がある場合にのみcloseします。

range-and-close.go
package main

import (
	"fmt"
)

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}
Run
0
1
1
2
3
5
8
13
21
34

capでバッファの長さを測っているようです。22

7.5. Select

select文は複数あるcaseのいずれかが準備できるようになるまでブロックします。
もし、複数のcaseの準備ができている場合ランダムに実行されます。

select.go
package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}
Run
0
1
1
2
3
5
8
13
21
34
quit

7.6. Default Selection

どのcaseも準備されておらず、defaultがあればそれが実行されます。

default-selection.go
package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}
Run
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!

7.7. Exercise: Equivalent Binary Trees

treeパッケージでは、以下のような構造体が定義されています。
type Tree struct { Left *Tree Value int Right *Tree }

  1. Walk関数を実装
  2. テスト

tree.New(k)は値( k, 2k, 3k, ..., 10k )をもつ、ランダムにソートされて構造化した二分木を生成します。
そして、そのチャネルから値を読み出し、10個の値を表示します。
1, 2, 3, ..., 10 という表示になります。

  1. t1,t2が一致しているかをWalkを使って調べるSame関数を実装
  2. テスト

Same(tree.New(1), tree.New(1))は、trueを返し、Same(tree.New(1), tree.New(2))は、falseを返すようにします。

exercise-equivalent-binary-trees.go
package main

import "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)

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool

func main() {
}

7.7.1. 解答例

  1. 深さ優先探索(DFS)を使用して、二分木の各ノードの値をチャネルに送信します。
  2. tree.New関数で二分木を生成し、Walk関数を起動し、チャネルから読み取りながら、最初の10個の値を出力します。
  3. Walk 関数を使用して2つの木の値をチャネルに送信し、2つのチャネルからの値を比較します。
  4. tree.New 関数で2つの二分木を生成し、 Same 関数を呼び出して、2つの木が同じ値を保存しているかどうかを確認します。
exercise-equivalent-binary-trees-ans.go
package main

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

func Walk(t *tree.Tree, ch chan int) {
	if t == nil {
		return
	}
	Walk(t.Left, ch)
	ch <- t.Value
	Walk(t.Right, ch)
}

func Same(t1, t2 *tree.Tree) bool {
	c1, c2 := make(chan int), make(chan int)
	go Walk(t1, c1)
	go Walk(t2, c2)
	for i := 0; i < 10; i++ {
		if <-c1 != <-c2 {
			return false
		}
	}
	return true
}

func main() {
	ch := make(chan int)
	go Walk(tree.New(1), ch)
	for i := 0; i < 10; i++ {
		fmt.Println(<-ch)
	}
	fmt.Println(Same(tree.New(1), tree.New(1))) // true
	fmt.Println(Same(tree.New(1), tree.New(2))) // false
}
Run
1
2
3
4
5
6
7
8
9
10
true
false

7.8. sync.Mutex

通信が必要ない場合や一度に一つのgoroutineだけが変数にアクセスしたい場合があります。
これらはmutual exclusion(排他制御)と言われ、一般的な名前はMutexです。
Golangは排他制御をsync.MutexLock,Unlockの二つのメソッドを持ちます。
Incメソッドのように排他制御で実行するコードをLock,Unlockで囲み定義します。
また、ValueメソッドのようにmutexがUnlockされることを保証するためにdeferを使用することも可能です。

mutex-counter.go
package main

import (
	"fmt"
	"sync"
	"time"
)

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mu.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mu.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}
Run
1000

1秒遅れて出力されました。

7.9. Exercise: Web Crawler

同一URLを取得することのないようにClawl関数を変更します。

exercise-web-crawler.go
package main

import (
	"fmt"
)

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)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
	// TODO: Fetch URLs in parallel.
	// TODO: Don't fetch the same URL twice.
	// This implementation doesn't do either:
	if depth <= 0 {
		return
	}
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	for _, u := range urls {
		Crawl(u, depth-1, fetcher)
	}
	return
}

func main() {
	Crawl("https://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{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

7.9.1. 解答例

IsFetchedにbool値を入れて取得済みの場合は即座にreturnするようにします。
また、sync.WaitGroupでチャネルの処理を書かずに待ってもらいます。

exercise-web-crawler.go
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)
	IsFetched(url string) bool
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
	if depth <= 0 || fetcher.IsFetched(url) {
		return
	}
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	wg.Add(len(urls))
	for _, u := range urls {
		go func(u string) {
			defer wg.Done()
			Crawl(u, depth-1, fetcher)
		}(u)
	}
}

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

// 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{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

var (
	fetched = make(map[string]bool)
	mu         sync.Mutex
	wg         sync.WaitGroup
)

func (f fakeFetcher) IsFetched(url string) bool {
	mu.Lock()
	_, ok := fetched[url]
	if !ok {
		fetched[url] = true
	}
	mu.Unlock()
	return ok
}
Run
found: https://golang.org/ "The Go Programming Language"
not found: https://golang.org/cmd/
found: https://golang.org/pkg/ "Packages"
found: https://golang.org/pkg/os/ "Package os"
found: https://golang.org/pkg/fmt/ "Package fmt"

7.10. まとめ

  • goroutine(ゴルーチン)はランタイムに管理される軽量なスレッドで、非同期処理を行う
  • channel型は、<-を用いて値の送受信が可能
  • ch := make(chan int)で生成
  • makeの第二引数にバッファの長さを与え、送受信のブロックができる
  • 受信の式の二つ目のパラメータで送り手のチャネルがcloseされているかわかる
  • 受信する値がなく、チャネルがcloseされているとき値はfalseとなる
  • select文は複数あるcaseのいずれかが準備できるようになるまでブロックします。
  • どのcaseも準備されておらず、defaultがあればそれが実行される
  • Golangは排他制御をsync.MutexLock,Unlockの二つのメソッドを持つ
GitHubで編集を提案

Discussion