wiki:GolangIterator

Go言語でイテレータが欲しかった(要らなくなった)

C→C++→Pythonというふうに暮らしてきた僕としては、Go言語にイテレータが無いのがつらかったのです。

C++だったらこんなふうでしたっけ。もう忘れました。すごく無意味な例。

  string s = "abcdefg";
  string ss;
  for (string::iterator it = s.begin(); it != s.end(); it++)
    ss += *it;

Pythonだったらこんなふうに。

  s = 'abcdefg'
  ss = ''
  for c in s:
    s += c

あるいはジェネレータを使ってこんなふうに。

def generator():
  yield 'a'
  yield 'b'
  yield 'c'
  yield 'd'
  yield 'e'
  yield 'f'
  yield 'g'

ss = ''
for c in generator():
  ss += c.upper()

それで、Go言語だとどうやるんでしょうか。 Go言語の教科書を見ると、チャネルを使えとか言われます。

func Iter() <- chan byte {
  ch := make(chan byte)
  go func() {
    ch <- 'a'
    ch <- 'b'
    ch <- 'c'
  }()
  return ch
}

  var ss string
  for c := range Iter() {
    ss += string(c)
  }

ちょっとしたイテレーションのために、ジェネレータとしてgoroutineを1個生成して、チャネル経由で流し込むってのは遅そうです。 メモリも使いそうだしなあ。それに、そのgoroutineの中でエラーが発生しても捕まえられません。

「めんどくさいけど、自分でイテレータを書くかー」なんて、以下のように使えるようなメソッドやらなんやらを考え始めます。

  for it = s.Iter(); it != nil; it = it.Next() {
    //...
  }

イテレータが必要な全ての型でこんな事をやってるとめんどくさいので、使いまわせる実装をネットで探したりします。 賢い人がいろいろと作ってくれています。しかし、interface{}で受けて、型アサーションやって、という具合で、これも遅そうだし、使いづらそうです。

・・・ここまで3週間くらい悩んで、見つけた解決。

func ForEach(s []byte, apply func(byte)) {
  for _, c := range(s) {
    apply(c)
  }
}

  var ss string
  ForEach("abcdefg", func(c byte) {
    ss += string(c)
  })

何のことは無い、ぐるぐる回す関数を作って、その関数に対して、手元の関数リテラルを渡してやればいいんです。 関数リテラルはクロージャなので、関数リテラルの外側の変数にアクセスできます。 これら、関数リテラルの外側の変数に結果を集めるような関数リテラルを書けばいいというわけです。 結局、イテレータは作らなかったけど、やりたい事ができたからこれでいいのかな。

どうやら、Go言語を使いこなすには、旧来の考え方に囚われていてはいけないようです。

あ。しろーとなんで、間違ってても叩かないでください。

(2013/9/4 - sgk)