【Go言語】関数にメソッドを定義する

# はじめに

普段は Java を書いているエンジニアですが、最近興味があって Go 言語を勉強中です。メソッドについて調べていたら「関数にメソッドが定義できる[^1]」という事に気付いたので、それを整理してみます。

[^1]: 「関数 *型* にメソッドが定義できる」の方が正確かもしれません。

最後に載せた例はジェネリクスが使えないと扱いにくいし、あまり Go 言語らしくない気がしています。なので、「Go 言語では一般的にこういうときに使う」とか、「そもそもあまり使い道ないよ」とかコメントで教えてもらえると嬉しいです。

# メソッドの基本

```go:基本構文
func (レシーバ) メソッド名(引数) {
  // 処理
}
```
Go言語のメソッドは、基本的には「レシーバが指定できる関数」です。
構造体を定義し、構造体をレシーバとするメソッドを定義することで、Javaなどのクラス *のようなもの* を作成することが可能です。

```go:メソッドの例
type Person struct {
	name string
}

// Person 型にメソッドを関連付ける
func (p Person) Hello() {
	fmt.Printf("Hello, I'm %s.\n", p.name)
}
```

このように定義したメソッドは `Person` 型のオブジェクトに対して `obj.Hello()` のような形で呼び出すことができます。

```go
p := Person{name: "Alice"}
p.Hello() // => Hello, I'm Alice.
```

ここで重要なことは、
Go言語のメソッドは **構造体** ではなく **型** に関連付ける、
ということです。

いくつかの制限がありますが、レシーバに指定できるものは構造体に限りません。

## レシーバに指定できるもの

[公式ドキュメント](https://golang.org/ref/spec#Method_declarations) によると、レシーバに対する条件は次の通り。

> A receiver base type cannot be a pointer or interface type and it must be defined in the same package as the method. 

「レシーバの基本型はポインタ型やインターフェース型でなく、メソッド定義と同じパッケージで定義された型でなければならない」とのこと。つまり、以下のような場合にはコンパイルエラーが発生します。

```go:コンパイルエラーになる例
func (p **Person) pointerMethod() {
	// *Person が型宣言されていないのでコンパイルエラー
	// invalid receiver type **Person (*Person is not a defined type)
}

type PersonPointer *Person

func (p PersonPointer) pointerTypeMethod() {
	// ポインタ型なのでコンパイルエラー
	// invalid receiver type PersonPointer (PersonPointer is a pointer type)
}

type Greeter interface {
	Hello()
	Goodbye()
}

func (g Greeter) interfaceMethod() {
	// インターフェース型なのでコンパイルエラー
	// invalid receiver type Greeter (Greeter is an interface type)
}

func (t time.Time) anotherPackageMethod() {
	// 他のパッケージなのでコンパイルエラー
	// cannot define new methods on non-local type time.Time
}
```

逆に言えば、これら以外の場合はエラーにならないので、関数などにもメソッドを関連付けることが可能です。

```go:関数にメソッドを関連付ける例
// 引数を一つ取り、戻り値がない関数型
type Consumer func(x interface{})

// 関数を適用する、というメソッドを定義
func (c Consumer) apply(x interface{}) {
	c(x)
}

// Consumer 型として関数宣言
c := Consumer(func(x interface{}) {
	fmt.Println(x)
})

// メソッド呼び出し
c.apply("hoge") // => hoge 
```

これだけだとなんの役に立つのかわかりませんが、たとえば次のような応用が考えられます。

# 応用例：メソッドチェーンで関数を合成する

コード: https://play.golang.org/p/aHs_FTOc0Er
Java の [Function](https://docs.oracle.com/javase/jp/13/docs/api/java.base/java/util/function/Function.html) を参考に、関数の合成を行うメソッドを定義してみました。

```go
// int 型を受け取り、int 型を返す関数型
type IntFunction func(x int) int

// 合成関数を返すメソッド
func (f IntFunction) AndThen(g IntFunction) IntFunction {
	// 「関数 f 、関数 g の順に適用する関数」を戻り値として返す
	return func(x int) int {
		return g(f(x))
	}
}
```

戻り値も `func(x int) int` を `IntFunction` 型として返しているので、これはメソッドチェーンで繰り返し呼ぶことが可能です。

```go:実行例
// 引数に 5 加える関数
func plusFive(x int) int {
	return x + 5
}

// 引数を二乗する関数
func square(x int) int {
	return x * x
}

// 引数を３倍する関数
func timesThree(x int) int {
	return x * 3
}

func main() {
	// f(x) = 3 * (x + 5) ^ 2
	f := IntFunction(plusFive).AndThen(square).AndThen(timesThree)

	for i := 0; i < 10; i++ {
		fmt.Printf("3 * (%d + 5) ^ 2 = %d\n", i, f(i))
	}
}

// 出力結果
// ------------------------
// 3 * (0 + 5) ^ 2 = 75
// 3 * (1 + 5) ^ 2 = 108
// 3 * (2 + 5) ^ 2 = 147
// 3 * (3 + 5) ^ 2 = 192
// 3 * (4 + 5) ^ 2 = 243
// 3 * (5 + 5) ^ 2 = 300
// 3 * (6 + 5) ^ 2 = 363
// 3 * (7 + 5) ^ 2 = 432
// 3 * (8 + 5) ^ 2 = 507
// 3 * (9 + 5) ^ 2 = 588
```