Golang(Go)/Rubyでジェネレータを実現する
こちらでPHPとPython3でのジェネレータを調べていた際に、GolangとRubyではどのように実現できるのか調査をしてみた。
PHP、Python3、Golang、Ruby2.4でジェネレータ
numbers.txt
を読み込み、以下の出力結果となるような簡単なジェネレータを用意した。
zero
one
two
three
four
five
0:zero
1:one
2:two
3:three
4:four
5:five
PHPの場合
PHP5.6で検証した。yield from
は今回は利用していないので、PHP7は選択しなかった。
<?php
function my_generator($name)
{
$file = fopen($name, "r");
if ($file) {
while ($line = fgets($file)) {
yield $line;
}
}
fclose($file);
}
$g = my_generator("numbers.txt");
foreach ($g as $k => $v) {
print($k. ":". $v);
}
Pythonの場合
PHPと同等な方法で実現できる。Pythonのバージョンは3.5を利用した。
def my_generator(name):
with open(name) as lines:
for line in lines:
yield line
g = my_generator("numbers.txt")
for k, v in enumerate(g):
print("%s:%s" % (k, v), end="")
Golangの場合
GolangにはPHPやPythonのyield
に相当した機能がない(参考はこちら)。そのため、ゴルーチン・チャンネルまたクロージャを利用した方法で類似のことを実現した。
ゴルーチン・チャンネルを利用した方法
numbers.txt
の行数が膨大になることを想定するとパフォーマンス面で懸念がある。
package main
import (
"bufio"
"fmt"
"os"
)
func yield(fp *os.File) chan string {
ch := make(chan string)
go func() {
defer close(ch)
scanner := bufio.NewScanner(fp)
for scanner.Scan() {
ch <- scanner.Text()
}
if err := scanner.Err(); err != nil {
panic(err)
}
} ()
return ch
}
func main() {
fp, err := os.Open("numbers.txt")
if err != nil {
panic(err)
}
defer fp.Close()
i := 0
for s := range(yield(fp)) {
fmt.Printf("%d:%s\n", i, s)
i++
}
}
クロージャを利用した方法
sync.Mutex
で同期をとる方法もありかもしれない。
package main
import (
"bufio"
"fmt"
"os"
)
func yield(fp *os.File) func() (bool, string) {
scanner := bufio.NewScanner(fp)
return func() (bool, string) {
t := scanner.Scan()
if err := scanner.Err(); err != nil {
panic(err)
}
return t, scanner.Text()
}
}
func main() {
fp, err := os.Open("numbers.txt")
if err != nil {
panic(err)
}
defer fp.Close()
y := yield(fp)
for i := 0;;i++ {
t, s := y()
if !t { break }
fmt.Printf("%d:%s\n", i, s)
}
}
Ruby2.4の場合
Rubyにもyield
があるが、PHP/Pythonのようなジェネレータではなくブロックを受け取る関数定義となる。PHP/Pythonのジェネレータに相当するような機能にEnumerator
があり、下記のよう記載する。
※kts_hさんからご提案いただいたコードに修正しました。
def my_generator(name)
return to_enum(__method__, name) unless block_given?
IO.foreach(name) do |line|
yield(line.chomp)
end
end
g = my_generator('numbers.txt')
g.with_index { |l, i| p '%d:%s' % [i, l] }
ここでは、ブロックがあるとブロックの処理をし、ブロックがないと Enumerator を返すメソッドとして扱うことができる。