A Tour of Go で学んだことまとめ
1. 概要
Go言語が利用されているプロダクトが増加傾向のようなので公式チュートリアルでもある「A Tour of Go」である程度学んでみたいと思います。そしてちょっと振り返りたいときにこれを自分で参考にします。
Welcome!
2. Welcome!
Tourの使い方を学びます。
2.1. Wello World
とりあえず実行してみなさいと言われたので実行しました。
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
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
らしいです。これで同一の実行結果を得ることが容易になるんですね。実行結果にも現れています。
常に最新の安定バージョンを利用できるそうなので何も考えずブラウザ上で実行すれば良さそうです。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Welcome to the playground!")
fmt.Println("The time is", time.Now())
}
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
パッケージです。
今回はfmt
とmath/rand
の二つのパッケージをimport
(インポート)しています。
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
My favorite number is 7
Go Playgroundでは時刻が一定なのでランダムに生成するrand
を用いても出力結果は一定(7)となります。
3.2. Imports
括弧でパッケージのインポートをグループ化しており、公式にもこの記法(factored import statement)が推奨されていますが、以下のように複数のimport文を書くこともできます。
import "fmt"
import "math"
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}
Now you have 2.6457513110645907 problems.
3.3. Exported names
一文字目が大文字で始まる名前は外部のパッケージから参照された名前(exported name)です。
反対に小文字で始まる名前は外部のパッケージから参照することができません。
そこで以下のプログラムを実行してみます。
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.pi)
}
./prog.go:9:19: undefined: math.pi
エラーが出てしまいました。「math.piは定義されてないよ」と言われています。小文字から始まる名前は外部パッケージから参照できませんので、math.Pi
に変更して実行してみます。
package main
import (
"fmt"
"math"
)
func main() {
- fmt.Println(math.pi)
+ fmt.Println(math.Pi)
}
3.141592653589793
無事に円周率が表示されました。
3.4. Functions
今回のadd関数はint型の2つの引数を取りますが、変数名の後ろに型名を記述します。
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
55
42+13の結果が出力されています。
3.5. Functions continued
複数の引数の型が同一の時、最後だけ残して省略することができます。例えば以下のようにします。
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))
}
55
3.6. Multiple results
複数の戻り値を返すこともできます。
以下のswap関数を見てみます。
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)
}
world hello
このように、a,bそれぞれに代入できています。
3.7. Named return values
戻り値となる変数に名前をつけることができます。こうすることで、returnのみで値を返すことができます。('naked return')
長い関数で利用すると読みやすさに悪影響を与えるため、短い関数でのみ利用するべきです。
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))
}
7 10
3.8. Variables
var文は変数を宣言することができます。これも関数の引数と同様に変数名の最後に型名を書くことで省略することができます。
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
0 false false false
ご覧の通り、関数内でもvar文は利用可能です。
3.9. Variables with initializers
varで変数を宣言する際に初期化子を与えることができます。その際、型を省略することができるようになります。
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)
}
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)
}
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
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)
}
Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)
3.12. Zero values
変数に初期値を与えない場合はゼロ値が代入されます。
型 | 値 |
---|---|
整数型 | 0 |
bool型 | false |
string型 | "" |
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)
}
0 0 false ""
3.13. Type conversions
変数v
、型T
があった時、型変換はT(v)
で行います。
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)
}
3 4 5
以下のように省略して記述することも可能です。
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)
}
3 4 5
3.14. Type inference
:=
やvar =
等で明示的な型を指定しない場合、右側の変数から型推論されます。
型を指定しない数値である場合、右側の定数の精度に基づいてint
,float64
,complex128
になります。
package main
import "fmt"
func main() {
v := 42 // change me!
fmt.Printf("v is of type %T\n", v)
}
v is of type int
change me!
と言われているため3.14に変更してみます。
package main
import "fmt"
func main() {
- v := 42 // change me!
+ v := 3.14 // change me!
fmt.Printf("v is of type %T\n", v)
}
v is of type float64
int
からfloat64
に変わりました。
3.15. Constants
定数はconst
を用いて宣言します。
ただし、:=
を利用することはできません。
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)
}
Hello 世界
Happy 3.14 Day
Go rules? true
3.16. Numeric Constants
型のない定数は状況により必要な型を取ることになります。
以下のプログラムでは、Big
,Small
に型が宣言されていないため計算結果により型が変わっています。
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))
}
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等の言語との違いとして、それらを囲う丸括弧()
が必要ないということです。
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
45
4.2. For continued
初期化と後処理は任意です。
不要な場合は;
のみ記述します。
package main
import "fmt"
func main() {
sum := 1
- for i := 0; i < 10; i++ {
+ for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
1024
4.3. For is Go's "while"
なんなら、;
を省略することだってできてしまいます。つまりC言語等のwhile文はGo言語におけるfor文であるのです。
package main
import "fmt"
func main() {
sum := 1
- for ; sum < 1000; {
+ for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
1024
4.4. Forever
条件式を省略すると無限ループとなります。
package main
func main() {
for {
}
}
4.5. If
if文にはfor文同様に丸括弧()
が必要ありません。
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))
}
1.4142135623730951 2i
4.6. If with a short statement
for文のように条件式の前に簡単な文を書くことができます。
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),
)
}
9 20
4.7. If and else
if文で宣言された変数はelse文でも用いることができます。
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),
)
}
27 >= 20
9 20
なお、main関数のPrintln
は先にpow関数が実行されてから実行されるため、pow関数のPrintf
が先に実行され、その後9 20
が出力されています。
4.8. Exercise: Loops and Functions
関数とループを利用して平方根を計算してみます。数値x
が与えられた時に幾つかのz
を推測し、z^2
がどれだけx
に近づいたかに応じてz
を調節できます。
package main
import (
"fmt"
)
func Sqrt(x float64) float64 {
}
func main() {
fmt.Println(Sqrt(2))
}
4.8.1. 解答例
math
パッケージをインポートして出力を比較してみました。
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
は定数でも整数でもなくて良いのです。
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)
}
}
Go runs on Linux.
4.10. Switch evaluation order
case
文は上から下に評価されるため、条件が一致するとそこでswitch
文は終了(自動的にbreak)します。
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.")
}
}
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
をシンプルに表現することが可能となります。
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.")
}
}
Good evening.
2009-11-10 23:00:00 UTC
は23時なのでGood evening.
が出力されています。
4.12. Defer
defer
文は元の関数の実行をreturn
するまで遅延させます。関数の引数はすぐに評価されますが、実行そのものはreturn
されるまで実行されません。
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
hello
world
4.13. Stacking defers
defer
を複数回利用した場合、それらはスタックされます。元の関数がreturn
されると、 LIFO
(last-in-first-out)の順で実行されます。
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
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
です。
ポインタ演算はありません。
主な仕様は以下のプログラムを見ればわかりますが、&
でアドレスを引き出し、*
でそのアドレスが指す変数を表します。
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
}
42
21
73
5.2. Structs
struct
(構造体)は、field
(フィールド)の集まりです。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
{1 2}
5.3. Struct Fields
field
は.
を用いてアクセスします。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
4
5.4. Pointers to structs
struct
のfield
はポインタを用いてアクセスすることもできます。
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)
}
{1000000000 2}
5.5. Struct Literals
:
で特定の値だけ初期化することができます。また、先頭に&
をつけると新たなstruct
へのポインタが代入されます。
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)
}
{1 2} &{1 2} {1 0} {0 0}
v2
ではX
のみ初期化しているのでY
はゼロ値(0)となりました。
5.6. Arrays
[n]T
型は、型T
のn
個の変数のarray
(配列)を表します。
例えばint型の10個の配列を作るには次のようにします。
var n [10]int
配列の長さは型の一部なので変更することはできません。
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)
}
Hello World
[Hello World]
[2 3 5 7 11 13]
配列そのものを出力する際は配列ということがわかるように[]
がついています。
5.7. Slices
型[]T
は型T
のslice
(スライス)を表します。
配列は固定長ですがスライスは可変長です。
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
[3 5 7]
このプログラムの場合、primes
の要素1から3が選択されています。Python
と同じような記法みたいです。
5.8. Slices are like references to arrays
slice
は配列へのポインタ(参照)です。つまりスライスの要素を変更すると配列の要素も変更されますし、同一の要素を共有しているスライスも変更されます。
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)
}
[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}
これは上記と同様の配列を作成し、それを参照するスライスを作成します。
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)
}
[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にあります。
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)
}
[3 5 7]
[3 5]
[5]
5.11. Slice length and capacity
スライスはlength
(長さ)とcapacity
(容量)の両方を持ちます。長さは要素の数で、容量はスライスの最初の要素から数えて元の配列の要素数です。
スライスs
の長さはlen(s)
容量はcap(s)
という関数で求められます。
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)
}
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]
それではスライスを拡張し、容量を超えてしまった場合はどうなるのでしょうか。
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)
}
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
で、当然ですが元となる配列もありません。
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
[] 0 0
nil!
5.13. Creating a slice with make
make
関数を使用することで、ゼロ化された配列を割り当て、それを指すスライスを返します。
第二引数に長さ、第三引数に容量を指定することができます。
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)
}
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
スライスの中にはスライスを含め他の型を入れることができます。
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], " "))
}
}
X _ X
O _ X
_ _ O
5.15. Appending to a slice
先ほども利用しましたが、スライスに新たな要素を追加するときは、append
関数を利用します。
要素を追加した際に容量をオーバーしてしまったときは新たに配列を割り当て直しスライスはそこを指すようになります。
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)
}
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
、もう一つはそのインデックスの要素のコピーです。
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)
}
}
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
インデックスや値は_
で捨てることができます。
また、二つ目の変数を省略することでインデックスだけを取り出すこともできます。
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)
}
}
1
2
4
8
16
32
64
128
256
512
5.18. Exercise: Slices
Pic
関数を実装してみます。
長さdy
のスライスに各要素が八ビットのuint型で長さdx
のスライスを割り当てたものを返す必要があります。実行すると画像が表示されます。
使用する関数の例として以下のようなものがおすすめされていました。
(x+y)/2
、x*y
、x^y
package main
import "golang.org/x/tour/pic"
func Pic(dx, dy int) [][]uint8 {
}
func main() {
pic.Show(Pic)
}
5.18.1. 解答例
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
関数は指定された型のマップを初期化し、使用可能な状態で返します。
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"])
}
{40.68433 -74.39967}
5.20. Map literals
mapリテラルはstructリテラルと似ていますが、key
(キー)が必要です。
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)
}
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
5.21. Map literals continued
mapに渡すトップレベルの型が単純な型名の場合、リテラルの要素から推定できるため省略が可能です。
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)
}
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
5.22. Mutating Maps
mapのキーに対する要素が存在する場合は二つ目の要素(boolean)で判断し、存在すればtrue
存在しなければfalse
で確認できます。
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)
}
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
5.23. Exercise: Maps
今回はWordCount
関数を実装します。
string
で渡される文章の各単語の出現回数のmapを返します。
wc.Test
関数はテストスイートを実行し成功か否かを出力します。
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. 解答例
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)
}
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
関数も変数の一種ですので、関数に関数を渡したり、関数値は関数の引数に取ることも、戻り値としても利用できます。
とてもややこしいですね。
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))
}
13
5
81
5.25. Function closures
Golangの関数はclosure
(クロージャ)で、関数内で定義された関数であり、外部変数を参照することができます。これにより状態を保持し、カプセル化出来ます。Javaとかやったことある人はわかりやすいかもしれません。
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),
)
}
}
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
フィボナッチ関数を実装します。
この関数は連続するフィボナッチ数を返す関数(クロージャ)を返します。
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. 解答例
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())
}
}
21
1
1
2
3
5
8
13
21
34
55
5.27. まとめ
- ポインタのゼロ値は
nil
-
&
でアドレス、*
で変数を表す - ポインタ演算は無い
-
struct
はfield
の集まり -
field
は.
を用いてアクセスする -
:
で特定の値だけ初期化することができる - 先頭に
&
をつけると新たなstruct
へのポインタが代入される -
[n]T
型は、型T
のn
個の変数のarray
を表す - 配列の長さを変えることはできない
- 型
[]T
は型T
のslice
を表す - スライスは可変長
-
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
型のレシーバを持つことを意味しています。
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())
}
5
6.2. Methods are functions
今回のAbs
は先ほどと同様の機能を維持したまま、通常の関数として記述しています。
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))
}
5
6.3. Methods continued
任意の型にもメソッドを宣言できます。
レシーバを伴うメソッドの宣言には、同一のパッケージ内にレシーバ型が存在する必要があります。
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())
}
1.4142135623730951
6.4. Pointer receivers
ポインタレシーバでメソッドを宣言することもできます。
レシーバ自身を変更したい際はポインタレシーバにします。
これはいわゆる破壊的処理と言われるようなものだと思います。
逆に非破壊的処理を求める場合は前回のような変数レシーバにする必要があります。
今回の場合、Scale
関数を変数レシーバ(v Vertex
)とするとRun
は5
となります。
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())
}
50
6.5. Pointers and functions
以下のコードは先ほどのAbs
とScale
メソッドを関数に書き直してあります。
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))
}
50
6.6. Methods and pointer indirection
メソッドがポインタレシーバの場合、呼び出し時に変数、ポインタどちらでもレシーバとして取ることができます。
v.scale(5)
はGoの場合利便性のため(&v).scale(5)
として処理されます。
関数の場合は特定の型を取る必要があります。
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)
}
{60 80} &{96 72}
6.7. Methods and pointer indirection (2)
メソッドが変数レシーバの場合も、変数、ポインタどちらでもレシーバとして取ることができます。
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))
}
5
5
5
5
6.8. Choosing a value or pointer receiver
ポインタレシーバを用いる利点は以下の通りです。
- 破壊的処理を実行する
- メソッドの呼び出し毎に変数のコピーを行わない
(2.)に関しては、大きな構造体の際に有効です。
一般的に、レシーバは値とポインタを混在させるべきではありません。
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())
}
Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25
6.9. Interfaces
interface
(インターフェース)は、メソッドのシグニチャの集まりで表されます。
そのメソッドの集まりを実装した値をインターフェース型の変数へ持たせることができます。
つまり以下の例でエラーが出るのは、Abs
メソッドが*Vertex
の定義であるため、Vertex
であるv
はa
へ代入することはできないためです。
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)
}
./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言語には、暗黙のインターフェース
というものがあり、これは異なるパッケージの、型が同じインターフェースを実装することが出来るようになっています。
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()
}
hello
6.11. Interface values
インターフェースは、値と型のタプルのようにして考えることができます。
(value, type)
値は、特定の基底となる具体的な型の値を保持し、メソッドを呼び出すとその型と同じメソッドが呼び出されます。
なんだかオーバーロードっぽい印象を受けました。
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)
}
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793
6.12. Interface values with nil underlying values
値がnil
の時、Go言語ではヌルポにはならずnil
をレシーバとして実行されます。nil
で呼び出されても適切に処理するよう記述しておくのが一般的とされています。
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)
}
(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello
6.13. Nil interface values
nil
インターフェースには具体的な値も型も存在しません。よってメソッドを呼び出すとエラーとなります。
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)
}
(<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{}
型の任意の引数を受け取ります。
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)
}
(<nil>, <nil>)
(42, int)
(hello, string)
6.15. Type assertions
type assertion
(型アサーション)は値の基になる具体的な値を利用する手段を提供します。
t := i.(T)
i
が型T
を保持しt
に代入されます。この時i
がT
を保有していなければpanic
を引き起こします。
t, ok := i.(T)
しかし上記のようにすると、ok
にi
がT
を保有しているかのboolean
が返され、保有していればtrue
を、そうでなければfalse
を返します。また、t
にはその値、もしくはゼロ値が代入されます。
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)
}
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
と書きます。
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)
}
Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!
6.17. Stringers
最もよく使われているインターフェースの一つに、stringer
というものがあります。
これはfmt
パッケージ含まれており、変数を文字列として出力するために使用されます。
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)
}
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")
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
する際の出力形式を変更してあげます。
また、ダブルコーテーション(""
)はバックスラッシュでエスケープします。
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)
}
}
loopback: "127.0.0.1"
googleDNS: "8.8.8.8"
6.19. Errors
Go言語はエラーの状態をerror
で表します。
error
型は以下のような組み込み型のインターフェースです。
type error interface { Error() string }
error
の取り扱いは値がnil
か否かで判断します。
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)
}
}
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
インタフェースを満たすようにします。
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
を文字列に変換し、ループに陥ることを防ぐことができます。
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))
}
1.414213562373095 <nil>
0 cannot Sqrt negative number: -2
6.21. Readers
io
パッケージはデータストリームを読むio.Reader
インターフェースを規定しています。
io.Reader
インターフェースはRead
メソッドを持ちます。与えられたバイトスライスへ入れ、バイトのサイズとエラーの値を返します。
終端はio.EOF
のエラーを返します。
今回は8byte毎に呼び出しています。
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
}
}
}
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
型を定義します。
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
を代入することができます。
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{})
}
OK!
6.23. Exercise: rot13Reader
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の暗号化を行います。
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)
}
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
パッケージに定義されています。
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())
}
(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
はひとつの色を返します。
生成する画像の色の値v
をcolor.RGBA{v, v, 255, 255}
を利用して返します。
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
の実装に基づいた画像が表示されます。
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
というキーワードを使用することで処理を非同期に実行することが可能となります。
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")
}
hello
world
world
hello
hello
world
world
hello
hello
7.2. Channels
channel
(チャネル)型は、<-
を用いて値の送受信ができます。
マップとスライスのように、チャネルは以下のように生成します
ch := make(chan int)
通常、片方が準備できるまで送受信はブロックされることで、goroutineの同期を可能にします。
2つのgoroutine
間で作業を分配し、両方の計算が完了すると、最終結果が計算されます。
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)
}
-5 17 12
7.3. Buffered Channels
チャネルはBuffer
(バッファ)として扱え、make
の第二引数にバッファの長さを与えることで初期化することができます。
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
1
2
ではこれをバッファが詰まるように変更してみます。
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)
}
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
します。
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)
}
}
0
1
1
2
3
5
8
13
21
34
cap
でバッファの長さを測っているようです。22
7.5. Select
select
文は複数あるcase
のいずれかが準備できるようになるまでブロックします。
もし、複数のcase
の準備ができている場合ランダムに実行されます。
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)
}
0
1
1
2
3
5
8
13
21
34
quit
7.6. Default Selection
どのcase
も準備されておらず、default
があればそれが実行されます。
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)
}
}
}
.
.
tick.
.
.
tick.
.
.
tick.
.
.
tick.
.
.
tick.
BOOM!
7.7. Exercise: Equivalent Binary Trees
tree
パッケージでは、以下のような構造体が定義されています。
type Tree struct { Left *Tree Value int Right *Tree }
-
Walk
関数を実装 - テスト
tree.New(k)
は値( k, 2k, 3k, ..., 10k )をもつ、ランダムにソートされて構造化した二分木を生成します。
そして、そのチャネルから値を読み出し、10個の値を表示します。
1, 2, 3, ..., 10 という表示になります。
-
t1
,t2
が一致しているかをWalk
を使って調べるSame
関数を実装 - テスト
Same(tree.New(1), tree.New(1))
は、true
を返し、Same(tree.New(1), tree.New(2))
は、false
を返すようにします。
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. 解答例
- 深さ優先探索(DFS)を使用して、二分木の各ノードの値をチャネルに送信します。
-
tree.New
関数で二分木を生成し、Walk
関数を起動し、チャネルから読み取りながら、最初の10個の値を出力します。 - Walk 関数を使用して2つの木の値をチャネルに送信し、2つのチャネルからの値を比較します。
- tree.New 関数で2つの二分木を生成し、 Same 関数を呼び出して、2つの木が同じ値を保存しているかどうかを確認します。
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
}
1
2
3
4
5
6
7
8
9
10
true
false
7.8. sync.Mutex
通信が必要ない場合や一度に一つのgoroutineだけが変数にアクセスしたい場合があります。
これらはmutual exclusion
(排他制御)と言われ、一般的な名前はMutex
です。
Golangは排他制御をsync.Mutex
とLock
,Unlock
の二つのメソッドを持ちます。
Inc
メソッドのように排他制御で実行するコードをLock
,Unlock
で囲み定義します。
また、Value
メソッドのようにmutexがUnlockされることを保証するためにdefer
を使用することも可能です。
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"))
}
1000
1秒遅れて出力されました。
7.9. Exercise: Web Crawler
同一URLを取得することのないようにClawl
関数を変更します。
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
でチャネルの処理を書かずに待ってもらいます。
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
}
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.Mutex
とLock
,Unlock
の二つのメソッドを持つ
Discussion