プログラミング言語 Go を読みながらメモ。
第一章 : https://qiita.com/Nabetani/items/077c6b4d3d1ce0a2c3fd
第二章 : https://qiita.com/Nabetani/items/d053304698dfa3601116
第三章 : https://qiita.com/Nabetani/items/2fd9c372fcd8383955a5
第四章 : https://qiita.com/Nabetani/items/59bfd00dc3323883a07f
第五章 : https://qiita.com/Nabetani/items/4b785f1c9b0b26d48475
で
なんでもレシーバ
基底型がポインタかインターフェースでない限り、メソッドは同じパッケージ内で定義されている全ての名前付き型に対して宣言することが出来ます
ならば関数をレシーバにしてみよう
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
func (p Point) Foo(q Point) float64 {
return math.Abs(q.X-p.X) + math.Abs(q.X-p.X)
}
type StrFunc func() string // 名前をつけないとレシーバになれない
func (f StrFunc) Call() {
fmt.Printf("<%s>\n", f())
}
func main() {
p := Point{1, 2}
fmt.Println(p.Foo(p)) // 普通な感じ
f := StrFunc(func() string { return "func" })
f.Call() // 関数をレシーバにする
}
まあ、いまのところ関数をレシーバにしたいことはあまりない気もするけど。あるのかなぁ。
生レシーバとポインタレシーバ
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
func (p Point) Hoge(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func (p *Point) Fuga(q *Point) float64 {
return math.Abs(q.X-p.X) + math.Abs(q.X-p.X)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
pp := &p
fmt.Println(p.Hoge(q))
fmt.Println(p.Fuga(&q)) // p を "&p" にする必要はないが、q を "&q" にする必要はある。
fmt.Println(pp.Hoge(q)) // &p でも呼べる。
fmt.Println(pp.Fuga(&q)) // これは当たり前
fmt.Println((&pp).Hoge(q)) // 無理。calling method Hoge with receiver &pp (type **Point) requires explicit dereference
fmt.Println((&pp).Fuga(&q)) // これも無理。calling method Fuga with receiver &pp (type **Point) requires explicit dereference
}
*Point
をレシーバとするメソッドに **Point
を渡してもコンパイルしてくれない。
名前をつければ行けるのかと思ったらそうでもない
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
type PPoint = *Point
func (p PPoint) Piyo(q *Point) float64 {
if p == nil || q == nil {
return 0
}
return math.Abs(q.X-p.X) + math.Abs(q.X-p.X)
}
func main() {
var pp PPoint
fmt.Println(pp.Piyo(pp)) // もちろん okay
fmt.Println((&pp).Piyo(pp)) // calling method Piyo with receiver &pp (type **Point) requires explicit dereference
}
いまひとつ気持ちがわからない。
例をもうひとつ。
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
func (p *Point) Baz(q Point) float64 {
return math.Abs(q.X-p.X) + math.Abs(q.X-p.X)
}
func main() {
p := Point{1, 2}
fmt.Println(p.Baz(p)) // 暗黙のアドレス取得
/*a*/ fmt.Println(Point{3, 4}.Baz(p)) // cannot call pointer method on Point literal
/*b*/ fmt.Println((&Point{3, 4}).Baz(p)) // okay
}
b が通るんなら a も通してくれればいいのに。
まあ、上記の a の例以外は、適当に書いたらうまくやってくれる感じだと思っている。
構造体埋め込みのある型をレシーバにする
やっぱり C++ の多重継承だ。という感じ。
暗黙の型変換がないだけか。
C++ 脳の私にわかりやすく書くと。
- レシーバ側は、基底クラスへの暗黙の型変換ができる。
- 引数には暗黙の型変換がない
ということか。コード例は以下の通り:
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
type PointWithInt struct {
Point
val int
}
func (p Point) Blah(q Point) float64 {
return math.Abs(p.X-q.X) + math.Abs(p.Y-q.Y)
}
func (p *Point) BlahP(q *Point) float64 {
return math.Abs(p.X-q.X) + math.Abs(p.Y-q.Y)
}
func main() {
p := PointWithInt{Point{1, 2}, 3}
q := PointWithInt{Point{2, 3}, 4}
fmt.Printf("%#v\n", p)
fmt.Printf("%#v\n", p.Blah(q)) // cannot use q (type PointWithInt) as type Point in argument to p.Point.Blah
fmt.Printf("%#v\n", p.Blah(q.Point)) //=> 2
fmt.Printf("%#v\n", p.BlahP(&q)) //cannot use &q (type *PointWithInt) as type *Point in argument to p.Point.BlahP
fmt.Printf("%#v\n", p.BlahP(&q.Point)) //=> 2
}
多重継承に近い仕組みがあるので、名前解決が難しい場合がある。
package main
import "fmt"
type A struct{}
type BA struct {
A
}
type C struct{}
type DBAC struct {
BA
C
}
func (a A) Kiwi() string { return "A.Kiwi" }
func (a A) Orange() string { return "A.Orange" }
func (c C) Orange() string { return "C.Orange" }
func main() {
d := DBAC{}
fmt.Println(d.Kiwi()) //=> A.Kiwi
fmt.Println(d.Orange()) //=> C.Orange
}
d.Orange()
が c.Orange()
に解決されるのが意外。
C++ だとエラーになる。
#include <iostream>
#include <string>
struct A{
std::string Kiwi() const { return "A::Kiwi"; }
std::string Orange() const { return "A::Orange"; }
};
struct B : public A{};
struct C{
std::string Orange() const { return "C::Orange"; }
};
struct D : public A, public C{};
int main(){
D d;
std::cout << d.Kiwi() << std::endl; //=> A::Kiwi
std::cout << d.Orange() << std::endl; // error: member 'Orange' found in multiple base classes of different types
return 0;
}
エラーのほうがいいと思うんだけどなぁ。Go の選択は危うい感じがする。
メソッド値
レシーバを束縛してメソッドを関数オブジェクトに変換できる。
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
func (p *Point) Banana() float64 {
return math.Max(math.Abs(p.X), math.Abs(p.Y))
}
func main() {
q := &Point{10, 20}
banana := q.Banana
fmt.Println(banana()) //=> 20
q.Y = 200
fmt.Println(banana()) //=> 200
q = &Point{123, 456}
fmt.Println(banana()) //=> 200
}
上記の例を見るとわかる通り、束縛されるのは値。変数ではない。同じことをクロージャでやると
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
func (p *Point) Banana() float64 {
return math.Max(math.Abs(p.X), math.Abs(p.Y))
}
func main() {
q := &Point{10, 20}
banana := func() float64 { return q.Banana() }
fmt.Println(banana()) //=> 20
q.Y = 200
fmt.Println(banana()) //=> 200
q = &Point{123, 456}
fmt.Println(banana()) //=> 456
}
と、最後の値が 456 になる。
カプセル化
大変低機能で素晴らしい。