[その1] Rubyプログラマー向けのGo言語の解説

http://www.sitepoint.com/go-rubyists-ii/

1 comment | 1 point | by WazanovaNews 4年弱前


Jshiike 4年弱前 | ▲upvoteする | link

Glenn Goodrichが、Rubyプログラマー向けにGo言語のinterfaceとWeb.goを紹介しています。1回目はまずは、interfaceから。

The Fallacy of Inheritance

継承は些細な修正も実装が面倒になり、コードが複雑になる可能性があります。例えば、Horseクラスと二つのサブクラス、GallopingHorseとSadHorseがあったとします。(その二つはステートの違いだけでなく、まったく性格の違うサブクラスかもしれません。)sadな雰囲気で、gallopをしているhorseがいる場合はどうするか?それぞれのクラスである振る舞いがロックアップされることになるかもしれません。また、type間の関係を考慮しなくてはいけなくなるので、コードに融通がきかなくなるという問題もでます。GolangのFAQでは、

“Object-oriented programming, at least in the best-known languages, involves too much discussion of the relationships between types, relationships that often could be derived automatically. Go takes a different approach.”

とあり、そうするとGoではどのようにしているのか?Goではinterfaceを使います。Rubyはinterfaceの概念はもってませんが、それをワークするようにはできます。本質的に、interfaceはcontractのようなもの。HorseクラスがAnimalというinterfaceを実装したら、Animal interfaceは、eat()とsleep()メソッドを要求します。Ruby的なコードを書いて説明しましょう。

#this is Pseudo-Ruby. We're pretending that Animal is an "interface"
#and Horse is a class. 
def full_day(animal)
  animal.eat()
  animal.sleep()
end

まず、full_dayメソッドの引数がAnimal interfaceを実装してなければ、Rubyはこのコードを実行しないふりをしましょう。それによって、Animal interfaceを満たすものだけを期待するメソッドを持つことになります。Horseなのか、Duckなのか、Personなのかは気にしません。本当のRubyは動的型付言語なので、interfaceは異なるかたちで適用されます。Animalがeatメソッド、sleepメソッドを持ってるかどうかに関わらず、コードはbyteコードに正しくコンパイルされます。しかし、Goは静的型付言語なので、interfaceはコンパイルの際に検証されます。

理論はここまでにして、サンプルをつくりましょう。

Animals

まずは、何らかのオブジェクトが必要になります。自らをHorseとしましょう。

package main
type Horse struct {
  name string
}
func main() {
}

type Horse structは、「新しい型をつくってます。それをHorseと呼んで、このようなstructにしなくてはいけません。」という意味。struct (= structure)は、データストラクチャー(string, int, array等)の集まり。Rubyのstructも、getterとsetterメソッドを付与してくれることを除いて、似ている。そのstructは、Horseの名前という情報を持っている。(Goにおいては、データの型は、データ名の後にくることに注意。)

次にHorseのメソッドを定義しよう。

package main
import (
  "fmt"
)
type Horse struct {
  name string
}
/* added a couple of methods here */
func (h *Horse) Sleep() {
  fmt.Println("zzz...") 
}
func (h *Horse) Eat() {
  fmt.Println("omnomnomnom")
}
func main() {
}

どのようにメソッドを定義しているか見てほしい。Rubyのようにクラス宣言ブロックの中でなく、Goではstructの外で宣言されています。また、その前方に(h *Horse)が置かれている。これによってコンパイラは、Horseクラスで呼び出せるメソッドがあるとわかり、このメソッドに対するHorseのインスタンスであるhが提供される。

Animal interfaceを追加してみよう。

package main
import (
  "fmt"
)
/* added an interface */
type Animal interface {
  Eat()
  Sleep()
}
type Horse struct {
  name string
}
func (h *Horse) Sleep() {
  fmt.Println("zzz...") 
}
func (h *Horse) Eat() {
  fmt.Println("omnomnomnom")
}
func main() {
}

数行のコードだが、全てのAnimalの振る舞い(eatとsleep)を定義している。Goのinterfaceは明示的には実装されない。Javaを書いたことがあれば、このようなコードは見たことがあるだろう。

class Horse implements Animal

Goでは必要とされるメソッドがあれば、interfaceは自動的に実装されている。つまり、HorseオブジェクトはAnimal interfaceを自動的に満たしていることになる。話しを先に進める前に、Georgeという名前のHorseのインスタンスを作成しよう。

package main
import (
  "fmt"
)
type Animal interface {
  Eat()
  Sleep()
}
type Horse struct {
  //changed the name of the field from
  //"name" to Name
  //fields that begin with an uppercased letter
  //are public fields.
  Name string
}
func (h *Horse) Sleep() {
  fmt.Println("zzz...") 
}
func (h *Horse) Eat() {
  fmt.Println("omnomnomnom")
}
func main() {
  /* create an instance */
  george := new(Horse)
  george.Name = "George"
  george.Sleep()
  george.Eat()
}

まず、nameというフィールドをNameに変更。大文字ではじまるフィールドはpublicの意味。mainメソッドに関しては、Horseへのリファレンスをするのに、newビルトインを使った。最後に、名前をつけて、EatSleepメソッドを呼び出した。もう少し面白くしてみよう。全てのanimalで機能するメソッドはどうだろうか?

func FullDay(a Animal) {
  a.Eat()
  a.Sleep()
}

GeorgeにおいてFullDay(george)を呼び出せば、以前と同じアウトプットがくるはず。これによって、Goでどのようにinterfaceが機能するかわかったと思う。データストラクチャーを決めて、メソッドを追加すると、interfaceが自動的に定義される。これでコードベースがフレキシブルになり、必要あらば簡単に修正できるようになる。

Go言語のMapの仕組みをマクロ視点で見てみる


#golang #ruby #コーディング

Back