2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

プログラミング言語Goを読みながらメモ(第六章)

Last updated at Posted at 2018-03-18

プログラミング言語 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

なんでもレシーバ

基底型がポインタかインターフェースでない限り、メソッドは同じパッケージ内で定義されている全ての名前付き型に対して宣言することが出来ます

ならば関数をレシーバにしてみよう

go
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() // 関数をレシーバにする
}

まあ、いまのところ関数をレシーバにしたいことはあまりない気もするけど。あるのかなぁ。

生レシーバとポインタレシーバ

go
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 を渡してもコンパイルしてくれない。

名前をつければ行けるのかと思ったらそうでもない

go
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
}

いまひとつ気持ちがわからない。
例をもうひとつ。

go
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++ 脳の私にわかりやすく書くと。

  • レシーバ側は、基底クラスへの暗黙の型変換ができる。
  • 引数には暗黙の型変換がない

ということか。コード例は以下の通り:

go
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
}

多重継承に近い仕組みがあるので、名前解決が難しい場合がある。

go
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++ だとエラーになる。

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 の選択は危うい感じがする。

メソッド値

レシーバを束縛してメソッドを関数オブジェクトに変換できる。

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
}

上記の例を見るとわかる通り、束縛されるのは値。変数ではない。同じことをクロージャでやると

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 := 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 になる。

カプセル化

大変低機能で素晴らしい。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?