A Tour of Goをやる
Imports
import "fm"
import "math"
は次のように書ける(facoterd import statement)
factoredは、「要素化、グループ化、整理済み」の意味。
import (
"fm"
"math"
)
Exported names
- 最初の名前が大文字で始まる名前は、エクスポート(公開)された名前(exported name)である
- これはインポートすると参照できる
- 例:
Pi
はmath
パッケージでエクスポートされる
- 対して、小文字ではじまる
pi
やhoge
などはエクスポートされていない
Functions
- 変数名の後ろに型を書く
- 型が複雑になったとき有用
Functions contined
x int, y int
を
x, y int
とかける
Multiple results
func foo(x, y string) (string, string) {
return y, x
}
のように複数の戻り値を指定できる
Named return values
- 戻り値となる変数に名前をつけることができる
- ただし、読みやすさの為に短い関数で使うのが良い
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
Variables
- varステートメントはパッケージ、関数で利用可能
- 複数の変数の最後に型を書いて、変数のリストを作成可能
var c, python, java bool
Variables with initializers
- var宣言で初期化ができる
- 初期化子が与えられている場合、型を省略すると、型は推論される
- FYI:
var 変数 型 = 初期化子
var i, j int = 1, 2
Short variable declarations
- 関数の中で、
var
の代わりに、:=
を使って、暗黙的な型宣言ができる - 関数の外では、
var
,func
が必要- 例えば、func main()の外でいきなり暗黙的な宣言ができない
func main() {
k : = 3
c, python, java := true, false, "no!"
}
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
は知らないけど一旦スルーする
- TODO:
- サイズ、符号なし整数を使いたいとかでない限り、
int
でOK- uintを使う例としては、RGBとか
-
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)
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:知らない型
Constatns(定数)
-
const
キーワードを使って宣言する - 文字(character)、文字列(string)、boolean、数値(numeric)のみで使える
-
:=
を使って宣言不可
Numeric Constants
- 数値の定数は高精度な値である
- 例はそれそ示す内容であった
Flow control statements: for, if, else, switch and defer
For
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
次で構成される
- 初期ステートメント
- 条件式
- 後処理ステートメント
For continued
sum := 1
for ; sum < 1000; {
sum += sum
}
初期化と後処理ステートメントの記述は任意である
For is Go's "while"
sum := 1
for sum < 1000 {
sum += sum
}
;
は省略可能。ゆえにGoにおいてwhile文はforが担う
Forever
for {
}
// timeout running program
//
// Program exited: status 1.
条件を省略すれば、無限ループをコンパクトに表現可能
If
if x < 0 {
return 'x < 0'
}
- ()は不要
- {}は必要
If with a short statement
if v := math.Pow(x, n); v < lim {
return v
}
条件ステートメントの前に1回だけ簡単なステートメントを書ける
If and else
if v := math.Pow(x, n); v < lim {
return v
} else {
return v + 1
}
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),
)
}
}
もうちょいシュッと書けないのかなとは思う
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は定数でなくても良い
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
例題の方にあったので、へぇとなった
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
のステートメントをシンプルに表現可能
Defer
defer fmt.Println("world")
fmt.Println("hello")
// hello
// world
-
defer
へ渡した関数は直ぐに実行される - が、呼び出し元の関数がreturnするまで実行されない
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(先入れ後出し)
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へ値を代入している
-
dereferencing
、indirecting
としてよく知られているらしい- dereferencing: デリファレンス。参照先を見に行くこと
- indirecting:リファレンス。変数のアドレスを他の変数に入れること
- C言語と異なり、ポインタ演算はない
Structs(構造体)
type Vertex struct {
X int
Y int
}
fmt.Println(Vertex{1, 2})
// {1 2}
-
struct
は、フィールドの集まり
type Vertex struct {
X, Y int
}
とも書ける
Struct Fields
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
// 4
-
.
でアクセスできる
Pointers to structs
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
// {1000000000 2}
- structのフィールドへは、ポインタ変数を通してもアクセス可能
-
(*p).X
と書けるが、Goではp.X
と書ける
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へのポインタを戻す
Arrays
var a [10]int
- 上記は、int10個の配列変数a
-
[n]T
型は、型T
のn
個の変数の配列 - 配列の長さも型の一部
- 配列サイズは変更不可である
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]
``
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]
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]
- スライスは配列への参照のようなもの
- スライスはどんなデータも格納せず、元の配列の部分列を指している
- なので、スライスの要素を変更すると、元の配列の対応する要素が変更される
Slice literals
[3]bool{true, true, false} // 配列リテラル
[]bool{true, true, false} // スライスリテラル
- スライスのリテラルは長さのない配列リテラルのようなもの
- 上記同様の配列を作成し、参照するスライスを作成している
Slice defaults
var a [10]int
// 全て等価
a[0:10]
a[:10]
a[0:]
a[:]
- スライスするときの既定値
- 下限0
- 上限スライス長
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を指定した場合は、容量が変わるようだ
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の容量をもち、元となる配列をもたない
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]
Slices of slices
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
board[0][0] = "X"
- スライスは、他のスライスを含む任意の型を含むことができる
- 上記は
[]string
スライスで構成されるスライスという具合
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]
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
Range continued
for i, _ := range pow
for _, value := range pow
- インデックスや値は、 " _ "(アンダーバー) へ代入することで捨てることができる
for i := range pow
- インデックスだけが必要なのであれば、2つ目の値を省略する
Exercise: Slices
wip
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"])
}
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に渡すトップレベルの型が単純な型名である場合、型名省略して、リテラルで推論可能
Mutating Maps
// mへ要素elemの挿入や更新
m[key] = elem
// 要素の削除
// elemは、map要素の型のゼロ値となる
delete(m, key)
// キーが存在するかは、2つ目の値で確認する(bool)
elem, ok = m[key]
// NOTE: `:=`で、varステートメント省略して宣言可能
elem, ok := m[key]
Exercise: Maps
wip
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))
}
Function closures
- Goの関数はクロージャ
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
変数にバインドされている
- 各クロージャは、それ自身の
Exercise: Fibonacci closure
wip
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
型のレシーバをもつ - 所感:巻き上げて構造体に関数を生やしていくイメージを持ちました
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))
}
- さっきのと同機能を通常の関数で記述している
- こっちのほうが慣れているが、さっきのはクラスの代わりということだろう
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
型の場合 - レシーバを伴うメソッドの宣言は、レシーバ型が同じパッケージにある必要がある
- 他パッケージに定義している型に対しては、レシーバを伴うメソッドを宣言できない
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変数がコピーされローカル変数になるから(という理解
- ポインタ変数(ポインタレシーバ)にすることで、元の値を書き換えられる
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が得られる(これを気づいて欲しかったのかな?) - 分からなくても次に進めという指示
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)
}
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()
として解釈される
- 上記の
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
メソッドはレシーバ自身を変更する必要がない- でもこのベストプラクティスに乗っかっている
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同様)
- インターフェースはメソッドのまとまり
- インターフェースを通してオブジェクトの振る舞いを定義することが可能
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キーワードは不要
- 暗黙のインタフェースは、インターフェースの定義をその実装から切り離す
- 事前の取り決めなしにパッケージに現せることができる
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)
}
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)
}
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)
}
The empty interface
interface{}
- ゼロ個のメソッドを指定されたインターフェース型は、空のインターフェースと呼ぶ
- 空のインターフェースは、任意の型の値を保持できる
- (全ての型は、少なくともゼロ個のメソッドを実装している)
- 空のインターフェースは、未知の型の値を扱うコードで使用される
-
fmt.Print
はinterface{}
型の任意の数の引数を受け取る
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)
}
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 falses, 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の日記
※判別の方も参考になる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 に置き換えられる
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インターフェースとして扱われる
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{}を使ったゼロ化された配列を作成することになる
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)
}
}
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
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] = ""
Exercise: Readers
wip
Exercise: rot13Reader
wip
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.Color
とcolor.Model
は共にインタフェースだが、定義済みのcolor.RGBA
とcolor.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
Exercise: Images
wip
Concurrency(並行処理)
Goroutines
並行処理周りは必要になったら帰ってくる
Channels
Buffered Channels
Range and Close
Select
Default Selection
Exercise: Equivalent Binary Trees
wip
Exercise: Equivalent Binary Trees
wip
sync.Mutex
Exercise: Web Crawler
wip
Where to Go from here...
- Go Documentation
-
Goのコード構成、動かし方を学ぶ動画
- あるいは、How to Write Go Code
- 標準ライブラリのヘルプが必要なら、パッケージリファレンス
- 言語自身のヘルプは、Go言語仕様
Goの並行性のモデルについて詳しくなるなら
- Go Concurrency Patterns (slides)
- Advanced Go Concurrency Patterns (slides)
- codewalk: Share Memory by Communicating
Webアプリケーションをはじめてみるなら
その他
- First Class Functions in Go
- Go Blog
- https://go.dev/ (旧golang.org)