Golangでエレガントだと思うこと
@kana さんとハッカソンしていて、Golangのどこが好きか? と聞かれた時にうまく説明できなかったのでまとめておきます。 よく、Golangはgoroutineとchannelが取り上げられることが多いと思いますが、 それよりも、僕がGolangをGolangたらしめていると考えているものとして、TypeとInterfaceの機構です。 Golangの思想は、他の言語ではこうなんだけど? ということを踏まえてFAQで簡単に説明されています。
http://golang.org/doc/faq#types
例えばFAQのTypesの章にあるサンプルコードで示されているように、
package main import "fmt" type Fooer interface { Foo() string ImplementsFooer() } type Bar struct {} func (b *Bar) Foo() string { return "bar" } func (b *Bar) ImplementsFooer() { fmt.Println("implements bar") } type Buzz struct {} func (b *Buzz) Foo() string { return "buzz" } func foo(o Fooer) { o.Foo() } func main() { bar := &Bar{} fmt.Println(bar.Foo()) bar.ImplementsFooer() foo(bar) buzz := &Buzz{} fmt.Println(buzz.Foo()) foo(buzz) // <- コンパイルエラー }
型Bar
は、関数Foo()
とImplementsFooer()
を実装しているのでBar
はFooer
を充足している、という表現になります。
Interfaceはよく、「Interfaceを実装する」という表現で使われます。
そのため、言語の文法レベルだと予約語を使ったり何かしら型やクラスに対して宣言するものが多いと思います。
Golangの場合だとInterfaceで定義されている関数を実装しているだけで、そのInterfaceとして振る舞えるので、
「Interfaceを充足している」という表現が適切だと思っています(FAQではsatisfyという英単語が使われています)。
サンプルコードに戻ってみると、関数foo()
は、引数に型Fooer
を要求しています。
つまり、Fooerを充足しているものであればなんでも渡せるわけです。
逆に、Fooerを充足していなければそこでコンパイルエラーとなります。
型Buzz
はFoo()
は実装していますが、ImplementsFooer()
を実装していないことをすぐに検出できます。
コンパイル時に漏れがないことがわかるので安全です。
よくあるDuck Typingのサンプルコード(Wikipediaよりhttp://en.wikipedia.org/wiki/Duck_typing#Implementations)を書いてみると:
package main import "fmt" type Duckable interface { Quack() Feathers() } type Duck struct{} func (d *Duck) Quack() { fmt.Println("Quaaaaaack !") } func (d *Duck) Feathers() { fmt.Println("The duck has white and gray feathers.") } type Person struct{} func (p *Person) Quack() { fmt.Println("The person imitates a duck.") } func (p *Person) Feathers() { fmt.Println("The person takes a feather from the ground and shows it.") } func InTheForest(duck Duckable) { duck.Quack() duck.Feathers() } func main() { donald := &Duck{} john := &Person{} InTheForest(donald) InTheForest(john) }
静的型付けにおけるDuck Typinpの一設計をエレガントな実装に落とし込めていると思ったので、僕のなかでGolang熱が高まっているわけですね。