Open89

A Tour of Goをやる

harapekoharapeko

見出しは章、小説単位です

Basics

Packages

  • Goのプログラムはパッケージで構成される
  • プログラムはmainパッケージから開始される
  • 規約
    • パッケージ名はインポートパスの最後の要素と同じ名前である
    • 例:math/randパッケージは、package randステートメントで始まるファイル群で構成する
  • その他
harapekoharapeko

Imports

import "fm"
import "math"

は次のように書ける(facoterd import statement)
factoredは、「要素化、グループ化、整理済み」の意味。

import (
    "fm"
    "math"
)
harapekoharapeko

Exported names

  • 最初の名前が大文字で始まる名前は、エクスポート(公開)された名前(exported name)である
    • これはインポートすると参照できる
    • 例:Pimathパッケージでエクスポートされる
  • 対して、小文字ではじまるpihogeなどはエクスポートされていない
harapekoharapeko

Functions

  • 変数名の後ろに型を書く
    • 型が複雑になったとき有用
harapekoharapeko

Multiple results

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

のように複数の戻り値を指定できる

harapekoharapeko

Named return values

  • 戻り値となる変数に名前をつけることができる
  • ただし、読みやすさの為に短い関数で使うのが良い
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}

func main() {
    fmt.Println(split(17))
}
harapekoharapeko

Variables

  • varステートメントはパッケージ、関数で利用可能
  • 複数の変数の最後に型を書いて、変数のリストを作成可能
var c, python, java bool
harapekoharapeko

Variables with initializers

  • var宣言で初期化ができる
  • 初期化子が与えられている場合、型を省略すると、型は推論される
  • FYI:var 変数 型 = 初期化子
var i, j int = 1, 2
harapekoharapeko

Short variable declarations

  • 関数の中で、varの代わりに、:=を使って、暗黙的な型宣言ができる
  • 関数の外では、var, funcが必要
    • 例えば、func main()の外でいきなり暗黙的な宣言ができない
func main() {
  k : = 3
  c, python, java := true, false, "no!"
}
harapekoharapeko

Basic types(基本型)

  • bool
  • string
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64, uintptr
  • byte // uint8の別名
  • rune // int32の別名。Unicodeのコードポイントを返す
    • Goでは文字そのものを荒らす為にrune。るねは古代文字を表す言葉(runes)
  • float32, float64
  • complex64, complex128
    factored宣言可能
var (
  Tobe bool = false
  MaxInt uint64 = 1<<64 - 1
  z complex128 = cmplx.Sqrt(-5 + 121)
)
  • FYI
    • int, uint, uintptrは32bitでは32bit。64bitでは64bit
      • TODO:uintptrは知らないけど一旦スルーする
    • サイズ、符号なし整数を使いたいとかでない限り、intでOK
      • uintを使う例としては、RGBとか
harapekoharapeko

Zero values(ゼロ値)

  • 数値型(int, floatなど):0
  • bool型:false
  • string型:""

Type conversions(型変換)

変数 v 、型 T があった場合、 T(v) は、変数 v を T 型へ変換します。
C言語とは異なり、Goでの型変換は明示的な変換が必要

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

よりシンプルに記述

i := 42
f := float64(i)
u := uint(f)
harapekoharapeko

Type inference

  • 型を明示せず宣言すると、右側の変数から型推論される
var i int
j := i // j is an int
i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128 ←TODO:知らない型
harapekoharapeko

Constatns(定数)

  • constキーワードを使って宣言する
  • 文字(character)、文字列(string)、boolean、数値(numeric)のみで使える
  • :=を使って宣言不可
harapekoharapeko

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

For

sum := 0
for i := 0; i < 10; i++ {
  sum += i
}

次で構成される

  • 初期ステートメント
  • 条件式
  • 後処理ステートメント
harapekoharapeko

For continued

sum := 1
for ; sum < 1000; {
  sum += sum
}

初期化と後処理ステートメントの記述は任意である

harapekoharapeko

For is Go's "while"

sum := 1
for sum < 1000 {
  sum += sum
}

;は省略可能。ゆえにGoにおいてwhile文はforが担う

harapekoharapeko

Forever

for {
}
// timeout running program
// 
// Program exited: status 1.

条件を省略すれば、無限ループをコンパクトに表現可能

harapekoharapeko

If with a short statement

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

条件ステートメントの前に1回だけ簡単なステートメントを書ける

harapekoharapeko

If and else

if v := math.Pow(x, n); v < lim {
  return v
} else {
  return v + 1
}
harapekoharapeko

Exercise: Loops and Functions

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() {
	for i := 1.0; i <= 10; i++ {
		fmt.Println(
			i,
			Sqrt(i),
			math.Sqrt(i),
		)
	}
}

もうちょいシュッと書けないのかなとは思う

harapekoharapeko

Switch

switch true {
case true:
	fmt.Println("true")
default:
	fmt.Println("false")
}

switch os := runtime.GOOS; os {
case "linux":
	fmt.Println("Linux.")
case "darwin":
	fmt.Println("OS X.")
default:
	// freebsd, openbsd,
	// plan9, windows...
	fmt.Printf("%s.\n", os)
}
  • caseは定数でなくても良い
harapekoharapeko

Switch evaluation order

switch i {
case 0:
case f():
}
  • i == 0 のとき、case0でbreakされ case f() は実行されない
today := time.Now().Weekday()
fmt.Println(today + 0) // Tuesday
fmt.Println(today + 1) // Wednesday

例題の方にあったので、へぇとなった

harapekoharapeko

Switch with no condition

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

冗長な if-then-else のステートメントをシンプルに表現可能

harapekoharapeko

Defer

defer fmt.Println("world")
fmt.Println("hello")
// hello
// world
  • deferへ渡した関数は直ぐに実行される
  • が、呼び出し元の関数がreturnするまで実行されない
harapekoharapeko

Stacking defers

fmt.Println("counting")
for i := 0; i < 3; i++ {
  defer fmt.Println(i)
}
fmt.Println("done")
// counting
// done
// 2
// 1
// 0
  • deferへ渡した関数が複数ある場合は、スタックされる
  • returnするとき、LIFO(後入れ先出し)になる
    • FILO(先入れ後出し)
harapekoharapeko

More types

Pointers

  • Goはポインタを扱う
    • ポインタ is 値のメモリアドレス
  • 変数Tのポインタは、*T
    • ゼロ値はnil
  • *オペレータは、ポインタの指す先の変数を示す
var p *int
i := 42
p = &i
  • &オペレータは、そのオペランド(演算子じゃない方)へのポインタを引き出す
  • 上記は、変数iの居場所を、ポインタ変数pに入れている
fmt.Println(*p) // ポインタ変数pを通してiから値を読み出し
*p = 21 // ポインタ変数pを通してiへ値を代入している
  • dereferencingindirectingとしてよく知られているらしい
    • dereferencing: デリファレンス。参照先を見に行くこと
    • indirecting:リファレンス。変数のアドレスを他の変数に入れること
  • C言語と異なり、ポインタ演算はない
harapekoharapeko

Structs(構造体)

type Vertex struct {
	X int
	Y int
}

fmt.Println(Vertex{1, 2})
// {1 2}
  • structは、フィールドの集まり
type Vertex struct {
	X, Y int
}

とも書ける

harapekoharapeko

Struct Fields

v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
// 4
  • .でアクセスできる
harapekoharapeko

Pointers to structs

v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
// {1000000000 2}
  • structのフィールドへは、ポインタ変数を通してもアクセス可能
  • (*p).Xと書けるが、Goではp.Xと書ける
harapekoharapeko

Struct Literals

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
)
  • フィールドの値を列挙することで、初期値を割り当てする
  • Name:構文を使って、フィールドの一部だけ列挙できる(順序不同)
  • 省略するとゼロ値が暗黙的に割当てられる
  • &を頭につけると、新しく割り当てられたstructへのポインタを戻す
harapekoharapeko

Arrays

var a [10]int
  • 上記は、int10個の配列変数a
  • [n]T型は、型Tn個の変数の配列
  • 配列の長さも型の一部
  • 配列サイズは変更不可である
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]
``
harapekoharapeko

Slices

  • 配列は固定長
  • スライスは可変長(配列よりも一般的)
  • []Tは型Tのスライスを表す
a[low : high]
  • 半開区間(左閉右開)を選択する
    • ※low <= x < high
primes := [6]int{2, 3, 5, 7, 11, 13}

var s []int = primes[1:4] // 要素の1, 2, 3
fmt.Println(s)
// [3 5 7]
harapekoharapeko

Slices are like references to arrays

names := [4]string{
  "John",
  "Paul",
  "George",
  "Ringo",
}
// [John Paul George Ringo]

a := names[0:2]
b := names[1:3]
// [John Paul] [Paul George]

b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
// [John XXX] [XXX George]
// [John XXX George Ringo]
  • スライスは配列への参照のようなもの
  • スライスはどんなデータも格納せず、元の配列の部分列を指している
  • なので、スライスの要素を変更すると、元の配列の対応する要素が変更される
harapekoharapeko

Slice literals

[3]bool{true, true, false} // 配列リテラル
[]bool{true, true, false} // スライスリテラル
  • スライスのリテラルは長さのない配列リテラルのようなもの
  • 上記同様の配列を作成し、参照するスライスを作成している
harapekoharapeko

Slice defaults

var a [10]int

// 全て等価
a[0:10]
a[:10]
a[0:]
a[:]
  • スライスするときの既定値
    • 下限0
    • 上限スライス長
harapekoharapeko

Slice length and capacity

  • スライスは、長さ(length)、容量(capacity)の両方をもつ
  • スライスの長さは、それに含まれる要素数
  • スライスの容量は、スライスの最初の要素から数えて、元となる配列の要素数
  • スライスs
    • 長さはlen(s)
    • 容量はcap(s)
s := []int{2, 3, 5, 7, 11, 13}
// len=6 cap=6 [2 3 5 7 11 13]

s = s[:0]
// len=0 cap=6 []

s = s[:4]
// len=4 cap=6 [2 3 5 7]

s = s[2:]
// len=2 cap=4 [5 7]
  • 容量以内であれば、スライスの長さを伸ばせる
  • 最後のを見ると、lowを指定した場合は、容量が変わるようだ
harapekoharapeko

Nil slices

var s []int
fmt.Println(s, len(s), cap(s))
// [] 0 0

if s == nil {
	fmt.Println("nil!")
}
// nil!
  • スライスのゼロ値はnil
  • nilスライスは0の長さ、0の容量をもち、元となる配列をもたない
harapekoharapeko

Creating a slice with make

a := make([]int, 5)  // len(a)=5
  • 組み込みのmake関数は、動的サイズの配列を作成する
  • ゼロ化された配列を割り当て、その配列を指すスライスを返す
b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
  • 3番目の引数で、容量を指定可能
  • cap(b)で容量を返す
a := make([]int, 5)
// len=5 cap=5 [0 0 0 0 0]

b := make([]int, 0, 5)
// len=0 cap=5 []

c := b[:2]
// len=2 cap=5 [0 0]

d := c[2:5]
// len=3 cap=3 [0 0 0]
harapekoharapeko

Slices of slices

board := [][]string{
	[]string{"_", "_", "_"},
	[]string{"_", "_", "_"},
	[]string{"_", "_", "_"},
}

board[0][0] = "X"
  • スライスは、他のスライスを含む任意の型を含むことができる
  • 上記は[]stringスライスで構成されるスライスという具合
harapekoharapeko

Appending to a slice

  • appendでスライスへ新しい要素を追加
  • func append(s []T, vs ...T) []T
    • s:追加元となるT型のスライス
    • vs:追加するT型の変数郡
  • 元の配列sが、変数郡を追加する時に容量が小さい場合、より大きいサイズの配列を割り当て直す
    • このとき、戻り値のスライスは、新しい割当先を示す
var s []int
// len=0 cap=0 []

// append works on nil slices.
s = append(s, 0)
// len=1 cap=1 [0]

// The slice grows as needed.
s = append(s, 1)
// len=2 cap=2 [0 1]

// We can add more than one element at a time.
s = append(s, 2, 3, 4)
// len=5 cap=6 [0 1 2 3 4]
harapekoharapeko

Range

  • forループで使用するrangeは、スライスやマップを反復処理するのに使う
  • スライスをrangeで繰り返す場合、rangeは反復毎に2つの変数を返す
    • 1つ目の変数:インデックス
    • 2つ目の変数:インデックスの場所の要素のコピー
var pow = []int{1, 2, 4}
for i, v := range pow {
	fmt.Printf("2**%d = %d\n", i, v)
}
// 2**0 = 1
// 2**1 = 2
// 2**2 = 4
harapekoharapeko

Range continued

for i, _ := range pow
for _, value := range pow
  • インデックスや値は、 " _ "(アンダーバー) へ代入することで捨てることができる
for i := range pow
  • インデックスだけが必要なのであれば、2つ目の値を省略する
harapekoharapeko

Maps

  • mapはキーと値を関連付けできる
  • ゼロ値はnil
  • 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"])
}
harapekoharapeko

Map literals continued

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

// mapリテラルで型や初期化を省略可能
var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}
  • mapに渡すトップレベルの型が単純な型名である場合、型名省略して、リテラルで推論可能
harapekoharapeko

Mutating Maps

// mへ要素elemの挿入や更新
m[key] = elem

// 要素の削除
// elemは、map要素の型のゼロ値となる
delete(m, key)

// キーが存在するかは、2つ目の値で確認する(bool)
elem, ok = m[key]

// NOTE: `:=`で、varステートメント省略して宣言可能
elem, ok := m[key]
harapekoharapeko

Function values

  • 関数も変数
  • 変数のように関数を渡すことも可能
    • 関数値を、関数の引数にしたり
    • 戻り値にできる
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))
}
harapekoharapeko

Function closures

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),
		)
	}
}
  • 例でadder関数はクロージャを返している
    • 各クロージャは、それ自身のsum変数にバインドされている
harapekoharapeko

Methods and interfaces

Methods

  • Goにクラスはない
  • 型にメソッドを定義できる
  • メソッドは、特別なレシーバ引数を関数に取る
  • レシーバは、funcキーワードとメソッド名の間に自身の引数リストで表現する
type Vertex struct {
	X, Y float64
}

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

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}
  • この例では、Absメソッドは、vという名前のVartex型のレシーバをもつ
  • 所感:巻き上げて構造体に関数を生やしていくイメージを持ちました
harapekoharapeko

Methods are functions

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))
}
  • さっきのと同機能を通常の関数で記述している
    • こっちのほうが慣れているが、さっきのはクラスの代わりということだろう
harapekoharapeko

Methods continued

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())
}
  • struct型だけでなく、任意の型にもメソッドを宣言可能
  • 上記は、MyFloat型の場合
  • レシーバを伴うメソッドの宣言は、レシーバ型が同じパッケージにある必要がある
    • 他パッケージに定義している型に対しては、レシーバを伴うメソッドを宣言できない
harapekoharapeko

Pointer receivers

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
}
  • *を外すと元のVartex変数は書き換えられず5になる
  • これはVartex変数がコピーされローカル変数になるから(という理解
  • ポインタ変数(ポインタレシーバ)にすることで、元の値を書き換えられる
harapekoharapeko

Pointers and functions

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))
}
  • 上記は、先程のメソッドを関数として置き換えたもの
    • GoでもJSと同じ感覚で、メソッドと関数を分けて捉えることが伺える
  • *を消してという指示。そしてエラー。
    • ./prog.go:23:8: cannot use &v (type *Vertex) as type Vertex in argument to Scale
  • コンパイルする為にはさらに何が必要?という問いかけ
    • ポインタ変数でなくなったので、&は使えないよね
  • &を消すと5が得られる(これを気づいて欲しかったのかな?)
  • 分からなくても次に進めという指示
harapekoharapeko

Methods and pointer indirection

var v Vertex
ScaleFunc(v, 5)  // Compile error!
ScaleFunc(&v, 5) // OK
  • 上記は、ポインタを渡さないといけないことがわかる
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK
  • ポインタレシーバを持つメソッドである場合
    • 呼び出し時のレシーバーは、ポインタかポインタを入れた変数のいずれかを取る
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK
  • v.Scale(5)のステートメントでは、vは変数であり、ポインタではない
  • このメソッドでは、ポインタレシーバが自動的に呼び出されている
  • Scaleメソッドが、ポインタレシーバを持つ場合、
    • v.Scale(5)のステートメントを(&v).Scale(5)として解釈する
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)と同じ
	v.Scale(2)
	ScaleFunc(&v, 10)

	// 予めポインタ変数にしておく
	p := &Vertex{4, 3}
	p.Scale(3)
	// NOTE:(*p)では?と思ったけど、レシーバの型と合わなくなるか
	ScaleFunc(p, 8)

	fmt.Println(v, p)
}
harapekoharapeko

Methods and pointer indirection (2)

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))
}
  • 関数の引数が変数である場合、特定の型の変数を取る必要がある
  • メソッドが変数レシーバである場合、変数かポインタのいずれかをレシーバとして取る
    • 上記のp.Abs()は、(*p).Abs()として解釈される
harapekoharapeko

Choosing a value or pointer receiver

ポインタレシーバを使う2つの理由

  • メソッドがレシーバを指す先の変数を変更する為
  • メソッドの呼び出し毎に変数のコピーを避ける為
    • 例:レシーバが大きな構造体の時に有効
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())
}
  • 一般的に、全メソッドは変数レシーバ、ポインタレシーバのどちらかで統一すべき
    • TODO:理由は数ページ先でわかるらしい
  • 例ではAbsメソッドはレシーバ自身を変更する必要がない
    • でもこのベストプラクティスに乗っかっている
harapekoharapeko

Interfaces

  • interface型は、メソッドのシグニチャの集まりで定義する
  • メソッドの集まりを実装した値を、interface型の変数に持たせる事ができる
  • 個人的にはTypeScriptのオーバーライドを変数に突っ込み
    • その変数に実施の値を突っ込むと対応するメソッドが呼び出せるって感じ
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)
}
  • こんな感じで型に対応するメソッドを用意してあげる
// Abserがインターフェース型名(と呼んでいいのか?
// Abs()に先のメソッド定義が集まる
type Abser interface {
	Abs() float64
}

func main() {
	var a Abser

	// aに入れることで、AbserインターフェースのAbs()メソッドが使える
	// 暗黙的にAbser型になるようだ
	f := MyFloat(-math.Sqrt2)
	a = f  // a MyFloat implements Abser

	v := Vertex{3, 4}
	a = &v // a *Vertex implements Abser

	// Vartexでなく、*Vartexの定義だからこれはエラー
	// a = v

	fmt.Println(a.Abs())
}
  • Goのインターフェースはメソッドの型だけを記述した型(Java同様)
  • インターフェースはメソッドのまとまり
  • インターフェースを通してオブジェクトの振る舞いを定義することが可能
harapekoharapeko

Interfaces are implemented implicitly

// インターフェースの定義
// 一旦の理解で、インターフェースに実装されたM()メソッドをこの後で吸着して実装するイメージ
type I interface {
	M()
}

type T struct {
	S string
}

// メソッドの実装
func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I = T{"hello"}
	i.M()
}
  • 型にメソッドを実装することで、型はインターフェースを実装する
  • インタフェースを実装することを明示的に宣言する必要はない
    • implementsキーワードは不要
  • 暗黙のインタフェースは、インターフェースの定義をその実装から切り離す
  • 事前の取り決めなしにパッケージに現せることができる
harapekoharapeko

Interface values

インターフェースの値は、値と具体的な型のタプルであると考えることができる

(value, type)
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)
	// (&{Hello}, *main.T)
	i.M()
	// Hello

	i = F(math.Pi)
	describe(i)
	// (3.141592653589793, main.F)
	i.M()
	// 3.141592653589793
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
harapekoharapeko

Interface values with nil underlying values

  • インターフェース自体の中にある具体的な値が nil の場合、メソッドは nil をレシーバーとして呼び出される
  • Go では nil をレシーバーとして呼び出されても適切に処理するメソッドを記述する
  • 具体的な値として nil を保持するインターフェイスの値それ自体は非 nil であることに注意
    • TODO:レシーバが呼び出されるだけってことかな?
type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	// nilをケアしてあげる
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
	var i I

	var t *T
	i = t
	describe(i)
	// (<nil>, *main.T)
	i.M()
	// <nil>

	i = &T{"hello"}
	describe(i)
	// (&{hello}, *main.T)
	i.M()
	// hello
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
harapekoharapeko

Nil interface values

  • nil インターフェースの値は、値も具体的な型も保持しない
  • 呼び出す 具体的な メソッドを示す型がインターフェースのタプル内に存在しないため、 nil インターフェースのメソッドを呼び出すと、ランタイムエラーにる
type I interface {
	M()
}

func main() {
	var i I
	describe(i)
	// (<nil>, <nil>)
	i.M()
	// panic: runtime error: invalid memory address or nil pointer dereference
	// [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47f241]
	// 
	// goroutine 1 [running]:
	// main.main()
	// 	/tmp/sandbox3804676939/prog.go:12 +0x61
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
harapekoharapeko

The empty interface

interface{}
  • ゼロ個のメソッドを指定されたインターフェース型は、空のインターフェースと呼ぶ
  • 空のインターフェースは、任意の型の値を保持できる
    • (全ての型は、少なくともゼロ個のメソッドを実装している)
  • 空のインターフェースは、未知の型の値を扱うコードで使用される
  • fmt.Printinterface{}型の任意の数の引数を受け取る
func main() {
	var i interface{}
	describe(i)
	// (<nil>, <nil>)

	i = 42
	describe(i)
	// (42, int)

	i = "hello"
	describe(i)
	// (hello, string)
}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}
harapekoharapeko

Type assertions

これは参考記事の方が詳しいので、この説明を引用させていただく
A Tour of Goの方だと、保持という言葉に踊らされて、i.(T)した時点で「型が決まる」と勘違いしてしまう
保持と言われるより、型が正しいかの真偽値と言われた方がしっくりきた
(ギミック的にはそうで、実際の値と確かめていたり、保持して具体的な値を利用する手段とやらを提供してくれるのかもだが、今はその理解は置いておく)

おさらいとして、インターフェース型の書き方

// 1
var i interface{}
i = true

// 2
var i interface{} = 100

// 3
i := interface{}("hello")

以下のようにすると、変数 s は文字列型になる

func main() {
i := interface{}("hello")
s := i.(string)
}

型が間違っているとpanic

func main() {
i := interface{}("hello")
s := i.(int) // panic!
}

panicしないよう型が合っているか確認するには、2つめの変数を受け取り、その真偽値で判定する
以下の型が間違っている変数 n はint型でゼロ値になる

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

n, ok := i.(int)
fmt.Println(n, ok) // 0 false

s, ok := i.(string)
fmt.Println(n, ok) // hello true
}

以上を踏まえると、A Tour of Goの方も理解しやすい

	// ここでインターフェース型で、文字列であることは分かっていないのかな?
	var i interface{} = "hello"

	// ここで文字列であることを断定。正しいのでprintされる
	s := i.(string)
	fmt.Println(s)

	// 真偽値で型が正しいことが分かる
	s, ok := i.(string)
	fmt.Println(s, ok)

	// 真偽値で型が正しくないことが分かる。また、ゼロ値が入る
	f, ok := i.(float64)
	fmt.Println(f, ok)

	// 真偽値を取っていないので、ゼロ値がなくpanic
	f = i.(float64) // panic
	fmt.Println(f)

引用元

👀Go言語(golang)のinterface{}を型アサーション/型キャスト - golangの日記
https://golang.hateblo.jp/entry/golang-interface-type-assertion
※判別の方も参考になる

harapekoharapeko

Type switches

switch v := i.(type) {
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}
  • 特定の型 T はキーワード type に置き換えられる
harapekoharapeko

Stringers

type Stringer interface {
    String() string
}
  • fmtパッケージに定義されているStringer
func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
  • Go言語では、定義されたメソッドを全て実装した時点で、暗黙的にインターフェースを実装したことになる
  • なので、String()を実装した時点で、Stringerインターフェースとして扱われる
harapekoharapeko

Exercise: Stringers

func (i IPAddr) String() string {
  return fmt.Sprintf(
  	"%d.%d.%d.%d",
    i[0], i[1], i[2], i[3],
  )
}

spread演算子みたいなシュッと書く方法はなさそうな
可変長な配列を考える必要はないと思うがrangeでリファクタリングすると次のようになる

func (ip IPAddr) String() string {
  tmp := make([]interface{}, len(ip))
  for i,x := range ip {
    tmp[i] = x
  }
  return fmt.Sprintf("%d.%d.%d.%d", tmp...)
}
  • いきなり、ip...のような変換はメモリ表現上できない
    • よって、中間スライスにコピーする必要がある
  • また、配列のインデックス操作を保証する必要がある
    • よって、[]interface{}を使ったゼロ化された配列を作成することになる
harapekoharapeko

Errors

  • Goでは、エラーの状態を error型で表現する
  • error 型は fmt.Stringer に似た組み込みのインタフェース
  • (fmt.Stringer同様、fmtパッケージは、変数を文字列で出力する際にerrorインターフェースを確認する)
type error interface {
    Error() string
}
  • errorがnil:エラーでない(成功)
  • errorがnilでない:エラーである(失敗)
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 with a short tatementでシュッと書ける
	if err := run(); err != nil {
		fmt.Println(err)
	}
}
harapekoharapeko

Exercise: Errors

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)
	}
	return 0, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}
  • float64(e)にする理由は、前ページにヒントが書いてある
    • fmt.Stringer`同様、fmtパッケージは、変数を文字列で出力する際にerrorインターフェースを確認する

    • Errorメソッド内で、fmt.Print(e)を呼び出すと、さらにErrorメソッドを呼んで…と無限ループになる
    • %fなどにして変換するのもありだが、-2でなくなるので
  • 以前の演習は手をつけていないので、一旦これでOK
harapekoharapeko

Readers

  • io パッケージは、データストリームを読むことを表現する io.Reader インタフェースを規定している
  • Goの標準ライブラリには、ファイル、ネットワーク接続、圧縮、暗号化などで、このインタフェースの 多くの実装 がある
  • io.Reader インタフェースは Read メソッドを持つ
func (T) Read(b []byte) (n int, err error)
  • Read は、データを与えられたバイトスライスへ入れ、入れたバイトのサイズとエラーの値を返す
  • ストリームの終端は、 io.EOF のエラーで返す
func main() {
	// strings.Readerを作成して
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
		// 8byteずつ読み出す
		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] = ""
harapekoharapeko

Images

  • image パッケージは、以下の Image インタフェースを定義している
package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}
  • (Bounds メソッドの戻り値である Rectangle は、 image パッケージの image.Rectangle に定義がある)

TODO:下記はどういう意味かわからない。恐らく練習問題に関係した話と思われる

  • color.Colorcolor.Model は共にインタフェースだが、定義済みの color.RGBAcolor.RGBAModel を使うことで、このインタフェースを無視できる
    • これらのインターフェースは、image/colorで定義されている
func main() {
	m := image.NewRGBA(image.Rect(0, 0, 100, 100))
	fmt.Println(m.Bounds())
	fmt.Println(m.ColorModel())
	fmt.Println(m.At(0, 0))
	fmt.Println(m.At(0, 0).RGBA())
}
// (0,0)-(100,100)
// &{0x47e4a0}
// {0 0 0 0}
// 0 0 0 0
harapekoharapeko

Concurrency(並行処理)

Goroutines

並行処理周りは必要になったら帰ってくる
https://go-tour-jp.appspot.com/concurrency/1

harapekoharapeko

Where to Go from here...

Goの並行性のモデルについて詳しくなるなら

Webアプリケーションをはじめてみるなら

その他