Goには、クラス( class )のしくみはありませんが、型にメソッド( method )を定義できます。
メソッドは、特別なレシーバ( receiver )引数を関数に取ります。
レシーバは、 func
キーワードとメソッド名の間に自身の引数リストで表現します。
この例では、 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()) }
メソッドは、レシーバ引数を伴う関数、でしたね?
この 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)) }
例で挙げたstructの型だけではなく、任意の型(type)にもメソッドを宣言できます。
例は、 Abs
メソッドを持つ、数値型の MyFloat
型です。
レシーバを伴うメソッドの宣言は、レシーバ型が同じパッケージにある必要があります。
他のパッケージに定義している型に対して、レシーバを伴うメソッドを宣言できません
(組み込みの int
などの型も同様です)。
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()) }
ポインタレシーバでメソッドを宣言できます。
これはレシーバの型が、ある型 T
への構文 *T
があることを意味します。
(なお、 T
は *int
のようなポインタ自身を取ることはできません)
例では *Vertex
に Scale
メソッドが定義されています。
ポインタレシーバを持つメソッド(ここでは Scale
)は、レシーバが指す変数を変更できます。
レシーバ自身を更新することが多いため、変数レシーバよりもポインタレシーバの方が一般的です。
Scale
の宣言(line 16)から *
を消し、プログラムの振る舞いがどう変わるのかを確認してみましょう。
変数レシーバでは、 Scale
メソッドの操作は元の Vertex
変数のコピーを操作します。
(これは関数の引数としての振るまいと同じです)。
つまり main
関数で宣言した Vertex
変数を変更するためには、Scale
メソッドはポインタレシーバにする必要があるのです。
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()) }
ここで、 Abs
と Scale
メソッドは関数として書きなおしてあります。
再度、line 16から *
を消してください。
なぜ振る舞いが変わったのかわかりますか?
コンパイルするために、さらに何が必要でしょうか。
(よくわからなくても、次のページに行きましょう)
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)) }
下の2つの呼び出しを比べると、ポインタを引数に取る ScaleFunc
関数は、ポインタを渡す必要があることに気がつくでしょう:
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
v.Scale(5)
のステートメントでは、 v
は変数であり、ポインタではありません。
メソッドでポインタレシーバが自動的に呼びだされます。
Scale
メソッドはポインタレシーバを持つ場合、Goは利便性のため、 v.Scale(5)
のステートメントを
(&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) }
逆にも見てみましょう。
変数の引数を取る関数は、特定の型の変数を取る必要があります:
var v Vertex fmt.Println(AbsFunc(v)) // OK fmt.Println(AbsFunc(&v)) // Compile error!
メソッドが変数レシーバである場合、呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取ることができます:
var v Vertex fmt.Println(v.Abs()) // OK p := &v fmt.Println(p.Abs()) // OK
この場合、 p.Abs()
は (*p).Abs()
として解釈されます。
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)) }
ポインタレシーバを使う2つの理由があります。
ひとつは、メソッドがレシーバが指す先の変数を変更するためです。
ふたつに、メソッドの呼び出し毎に変数のコピーを避けるためです。 例えば、レシーバが大きな構造体である場合に効率的です。
例では、 Abs
メソッドはレシーバ自身を変更する必要はありませんが、 Scale
と Abs
は両方とも
*Vertex
型のレシーバです。
一般的には、変数レシーバ、または、ポインタレシーバのどちらかですべてのメソッドを与え、混在させるべきではありません。 (この理由は数ページ後にわかります)
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()) }
interface(インタフェース)型は、メソッドのシグニチャの集まりで定義します。
そのメソッドの集まりを実装した値を、interface型の変数へ持たせることができます。
注意: この例は、22行目でエラーになります。
Abs
メソッドが、 Vertex
ではなく *Vertex
の定義であり、
Vertex
が Abser
インタフェースを実装していないということになるためエラーとなります。
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) }
型にメソッドを実装していくことによって、インタフェースを実装(満た)します。 インタフェースを実装することを明示的に宣言する必要はありません( "implements" キーワードは必要ありません)。
暗黙のインターフェースは、インターフェースの定義をその実装から切り離します。 インターフェースの実装は、事前の取り決めなしにパッケージに現れることがあります。
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() }
下記のように、インターフェースの値は、値と具体的な型のタプルのように考えることができます:
(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) }
インターフェース自体の中にある具体的な値が nil の場合、メソッドは nil をレシーバーとして呼び出されます。
いくつかの言語ではこれは null ポインター例外を引き起こしますが、Go では nil をレシーバーとして呼び出されても適切に処理するメソッドを記述するのが一般的です(この例では M
メソッドのように)。
具体的な値として 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 インターフェースの値は、値も具体的な型も保持しません。
呼び出す 具体的な メソッドを示す型がインターフェースのタプル内に存在しないため、 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) }
ゼロ個のメソッドを指定されたインターフェース型は、 空のインターフェース と呼ばれます:
interface{}
空のインターフェースは、任意の型の値を保持できます。 (全ての型は、少なくともゼロ個のメソッドを実装しています。)
空のインターフェースは、未知の型の値を扱うコードで使用されます。
例えば、 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) }
型アサーション は、インターフェースの値の基になる具体的な値を利用する手段を提供します。
t := i.(T)
この文は、インターフェースの値 i
が具体的な型 T
を保持し、基になる T
の値を変数 t
に代入することを主張します。
i
が T
を保持していない場合、この文は panic を引き起こします。
インターフェースの値が特定の型を保持しているかどうかを テスト するために、型アサーションは2つの値(基になる値とアサーションが成功したかどうかを報告するブール値)を返すことができます。
t, ok := i.(T)
i
が T
を保持していれば、 t
は基になる値になり、 ok
は真(true)になります。
そうでなければ、 ok
は偽(false)になり、 t
は型 T
のゼロ値になり panic は起きません。
この構文と map から読み取る構文との類似点に注意してください。
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) }
型switch はいくつかの型アサーションを直列に使用できる構造です。
型switchは通常のswitch文と似ていますが、型switchのcaseは型(値ではない)を指定し、それらの値は指定されたインターフェースの値が保持する値の型と比較されます。
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 }
型switchの宣言は、型アサーション i.(T)
と同じ構文を持ちますが、特定の型 T
はキーワード type
に置き換えられます。
このswitch文は、インターフェースの値 i
が 型 T
または S
の値を保持するかどうかをテストします。
T
および S
の各caseにおいて、変数 v
はそれぞれ 型 T
または S
であり、
i
によって保持される値を保持します。
defaultの場合(一致するものがない場合)、変数 v
は同じインターフェース型で値は i
となります。
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) }
もっともよく使われているinterfaceの一つに fmt
パッケージ に定義されている Stringer
があります:
type Stringer interface { String() string }
Stringer
インタフェースは、stringとして表現することができる型です。
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) }
IPAddr
型を実装してみましょう
IPアドレスをドットで4つに区切った( dotted quad )表現で出力するため、 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) } }
Goのプログラムは、エラーの状態を error
値で表現します。
error
型は fmt.Stringer
に似た組み込みのインタフェースです:
type error interface { Error() string }
( fmt.Stringer
と同様に、 fmt
パッケージは、変数を文字列で出力する際に error
インタフェースを確認します。 )
よく、関数は error
変数を返します。そして、呼び出し元はエラーが nil
かどうかを確認することでエラーをハンドル(取り扱い)します。
i, err := strconv.Atoi("42") if err != nil { fmt.Printf("couldn't convert number: %v\n", err) return } fmt.Println("Converted integer:", i)
nil の error
は成功したことを示し、 nilではない error
は失敗したことを示します。
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) } }
Sqrt
関数を 以前の演習 からコピーし、 error
の値を返すように修正してみてください。
Sqrt
は、複素数をサポートしていないので、負の値が与えられたとき、nil以外のエラー値を返す必要があります。
新しい型:
type ErrNegativeSqrt float64
を作成してください。
そして、 ErrNegativeSqrt(-2).Error()
で、 "cannot Sqrt negative number: -2"
を返すような:
func (e ErrNegativeSqrt) Error() string
メソッドを実装し、 error
インタフェースを満たすようにします。
注意: Error
メソッドの中で、 fmt.Sprint(e)
を呼び出すことは、無限ループのプログラムになることでしょう。
最初に fmt.Sprint(float64(e))
として e
を変換しておくことで、これを避けることができます。
なぜでしょうか?
負の値が与えられたとき、 ErrNegativeSqrt
の値を返すように Sqrt
関数を修正してみてください。
package main import ( "fmt" ) func Sqrt(x float64) (float64, error) { return 0, nil } func main() { fmt.Println(Sqrt(2)) fmt.Println(Sqrt(-2)) }
io
パッケージは、データストリームを読むことを表現する io.Reader
インタフェースを規定しています。
Goの標準ライブラリには、インタフェース、ファイル、ネットワーク接続、圧縮、暗号化、などで 多くの実装 があります。
io.Reader
インタフェースは Read
メソッドを持ちます:
func (T) Read(b []byte) (n int, err error)
Read
は、データを与えられたバイトスライスへ入れ、入れたバイトのサイズとエラーの値を返します。
ストリームの終端は、 io.EOF
のエラーで返します。
例のコードは、 strings.Reader
を作成し、
8 byte毎に読み出しています。
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 } } }
ASCII文字 '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{}) }
よくあるパターンは、別の io.Reader
をラップし、ストリームの内容を何らかの方法で変換するio.Readerです。
例えば、 gzip.NewReader は、
io.Reader
(gzipされたデータストリーム)を引数で受け取り、 *gzip.Reader
を返します。
その *gzip.Reader
は、 io.Reader
(展開したデータストリーム)を実装しています。
io.Reader
を実装し、 io.Reader
でROT13 換字式暗号( substitution cipher )をすべてのアルファベットの文字に適用して読み出すように
rot13Reader
を実装してみてください。
rot13Reader
型は提供済みです。
この Read
メソッドを実装することで io.Reader
インタフェースを満たしてください。
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) }
image
パッケージは、以下の
Image
インタフェースを定義しています:
package image type Image interface { ColorModel() color.Model Bounds() Rectangle At(x, y int) color.Color }
Note: Bounds
メソッドの戻り値である 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()) }
前に解いた、画像ジェネレーターを覚えていますか?
今回は、データのスライスの代わりに 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) }
この章はこれで終わりです。