@kyanny's blog

My thoughts, my life. Views/opinions are my own.

ES6 Generator と Ruby の Fiber と Python 2 の generator

CodeGrid で ES6 Generator の記事を読んだ。

ECMAScript 6の新機能 - Generator | CodeGrid

Ruby の Fiber のようなものなのかな、と思って書き比べてみたら少し違うところがあった。

function* generatorFunc(a) {
  console.log(a);
  var b = yield 1;
  console.log(b);
  var c = yield 2;
  console.log(c);
}

var generator = generatorFunc('a');
console.log(generator.next('b'));
console.log(generator.next('c'));
console.log(generator.next('d'));

この結果は

$ node -v
v0.12.0
$ node --harmony generator.js
a
{ value: 1, done: false }
c
{ value: 2, done: false }
d
{ value: undefined, done: true }

ES6 Generator では初回の next() 呼び出しに引数を渡しても捨てられてしまう(ように見える)。そういうものなのだと CodeGrid の記事にも書いてあったし、初回の next() に渡した引数を利用する方法もドキュメントには書いてあるのかもしれないが、 Ruby の Fiber だとそういう振る舞いにならなかった。

generator = Fiber.new do |a|
  puts a
  b = Fiber.yield 1
  puts b
  c = Fiber.yield 2
  puts c
end

puts generator.resume 'a'
puts generator.resume 'b'
puts generator.resume 'c'
puts generator.resume 'd'

これの結果は

$ ruby -v
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
$ ruby generator.rb
a
1
b
2
c

generator.rb:12:in `resume': dead fiber called (FiberError)
        from generator.rb:12:in `<main>'

最後の resume で例外になるのはいいとして。同じような機能のように思えるのに、同じように作られていないのはなぜなんだろう、と不思議に感じた。


2015/03/06 追記

Python にもジェネレーターってのがあったよなと思って書きくらべてみたらいろいろ勉強になった。

$ python -V
Python 2.7.9

next() に引数を渡すことができない。

def generatorFunc(a):
    print a
    b = yield 1
    print b
    c = yield 2
    print c

generator = generatorFunc('a')
print generator.next('b')
print generator.next('c')
print generator.next('d')
$ python generator.py 
Traceback (most recent call last):
  File "generator.py", line 9, in <module>
    print generator.next('b')
TypeError: expected 0 arguments, got 1

ジェネレータに外部から値を渡したいときは send() を使うようだ。だが、初回の send() の呼び出し時に渡す引数には制限があるようだ。

def generatorFunc(a):
    print a
    b = yield 1
    print b
    c = yield 2
    print c

generator = generatorFunc('a')
print generator.send('b')
print generator.send('c')
print generator.send('d')
$ python generator.py 
Traceback (most recent call last):
  File "generator.py", line 9, in <module>
    print generator.send('b')
TypeError: can't send non-None value to a just-started generator

エラーメッセージに従い None を渡したらうまくいった。

def generatorFunc(a):
    print a
    b = yield 1
    print b
    c = yield 2
    print c

generator = generatorFunc('a')
print generator.send(None)
print generator.send('c')
print generator.send('d')
$ python generator.py 
a
1
c
2
d
Traceback (most recent call last):
  File "generator.py", line 11, in <module>
    print generator.send('d')
StopIteration

ES6 の Generator のように受け取った値を黙って無視するよりは、 Python 2 のように例外が出るほうがへんに悩まずに済みそうで好みだ(ES6 の立場では、言語仕様書に書いてあるから熟読してから使え、ということなのかもしれない)