はじめに
普段は Java を書いているエンジニアですが、最近興味があって Go 言語を勉強中です。メソッドについて調べていたら「関数にメソッドが定義できる1」という事に気付いたので、それを整理してみます。
最後に載せた例はジェネリクスが使えないと扱いにくいし、あまり Go 言語らしくない気がしています。なので、「Go 言語では一般的にこういうときに使う」とか、「そもそもあまり使い道ないよ」とかコメントで教えてもらえると嬉しいです。
メソッドの基本
func (レシーバ) メソッド名(引数) {
// 処理
}
Go言語のメソッドは、基本的には「レシーバが指定できる関数」です。
構造体を定義し、構造体をレシーバとするメソッドを定義することで、Javaなどのクラス のようなもの を作成することが可能です。
type Person struct {
name string
}
// Person 型にメソッドを関連付ける
func (p Person) Hello() {
fmt.Printf("Hello, I'm %s.\n", p.name)
}
このように定義したメソッドは Person
型のオブジェクトに対して obj.Hello()
のような形で呼び出すことができます。
p := Person{name: "Alice"}
p.Hello() // => Hello, I'm Alice.
ここで重要なことは、
Go言語のメソッドは 構造体 ではなく 型 に関連付ける、
ということです。
いくつかの制限がありますが、レシーバに指定できるものは構造体に限りません。
レシーバに指定できるもの
公式ドキュメント によると、レシーバに対する条件は次の通り。
A receiver base type cannot be a pointer or interface type and it must be defined in the same package as the method.
「レシーバの基本型はポインタ型やインターフェース型でなく、メソッド定義と同じパッケージで定義された型でなければならない」とのこと。つまり、以下のような場合にはコンパイルエラーが発生します。
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
}
逆に言えば、これら以外の場合はエラーにならないので、関数などにもメソッドを関連付けることが可能です。
// 引数を一つ取り、戻り値がない関数型
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 を参考に、関数の合成を行うメソッドを定義してみました。
// 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
型として返しているので、これはメソッドチェーンで繰り返し呼ぶことが可能です。
// 引数に 5 加える関数
func plusFive(x int) int {
return x + 5
}
// 引数を二乗する関数
func square(x int) int {
return x * x
}
// 引数を3倍する関数
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
-
「関数 型 にメソッドが定義できる」の方が正確かもしれません。 ↩