LoginSignup
15
14

More than 3 years have passed since last update.

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

Posted at

はじめに

普段は 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) intIntFunction 型として返しているので、これはメソッドチェーンで繰り返し呼ぶことが可能です。

実行例
// 引数に 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

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

15
14
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
15
14