2013-05-08

Go の型は First-class ではない

Go の型は First-class ではない、ということにゴールデンウィーク最終日に気づき、悶々としています。(何も Go が悪いわけではない)

ことの発端は、以下の様な関数を定義したところから始まります。

func getName(x interface{}) string {
  return reflect.TypeOf(x).Elem().Name()
}

この関数に、任意の型のポインタをわたすと、型の名前を取得できます。けど、ポインタを渡さなければならないのですね。

  var foo *Foo
  name := getName(foo)

foo は使わないのに! 使わないというのはウソですね。けど、ちょっと違うんですよ。まあこれは私がPython 脳だからであって、別にGoが悪いわけじゃない。

何をしたいかと言うとですね、Google App Engine datastore のクエリの生成をラップしたいのです。標準ライブラリを使うと、以下のように書きます。

  c := appengine.NewContext(r)
  q := datastore.NewQuery("Foo")  // ☜(◉ɷ◉ )
  q.Filter("Y =", "xaxtsuxo")
  foos := make([]Foo, 0, 10)
  keys, err := q.GetAll(c, &foos)

Foo が型が定義されているのに、文字列で "Foo" を渡すのが悔しいわけです。そこはコンパイル時にスペルミスをひっかけて欲しいわけですよ。ストアされているデータと、Foo の定義が違うと、それはそれで実行時エラーになるんだけど。なので、

func createQuery(AnyType) *datastore.Query

のような関数を定義したいな、と思ったのですね。ところが、型は渡せないのです。以下のような方法に落ち着きます。

func createQuery(x interface{}) *datastore.Query {
  kind := reflect.TypeOf(x).Elem().Name()
  q := datastore.NewQuery(kind)
  return q
}

…

  var foo *Foo
  q := createQuery(foo).Filter("Y =", "xaxtsuxo")
  var foos := make([]Foo, 0, 10)
  keys, err := q.GetAll(c, &foos)

あぁ foo 使わないのに。いや、 配列の foos があるんで、そっち使えよって話なんだけど。なんなら GetAll せずに、foo を使ってイテレートするのが筋なのかも知れません。

ああ、そんなこと言い出したら、Filter にだって文字列を渡してるぞ。そう考えると GAE/Python の NDB はよくできてるなぁ、あのライブラリ作った人は Python のことよく分かってるなぁ。薄いラッパにしたかったんだけど、やっぱり厚くなってしまうのかなぁ。PropertyList とか PropertyLoadSaver をうまく使ったらよいのか? ぎゃぁ。というわけで連休が終わってしまったので、ペースダウンしていきます。