RubyのyieldとPythonのyieldの違い
RubyのyieldとPythonのyieldは実は全然違うような気がしてきたので、整理のために書いてみます。 まず、Rubyでeachに相当する物を自分で実装してみました。
# Ruby 1
class Array
def my_each
self.each{|x|
yield x
}
end
end
[1, 2, 3, 4, 5].my_each{|x|
puts x
}
これに相当する物をPythonで書くと、実はyieldを使いません。
# Python 1
class Array(list):
def each(self, f):
for i in self:
f(i)
def myprint(x):
print x
Array([1, 2, 3, 4, 5]).each(
myprint
)
これをもっとRuby 1のコードに近づけて書くと以下のようになります。Pythonにはputsに相当する関数がsys.stdout.writeという長ったらしい物なのでputsは自分で定義していますがそこは本質ではありません。重要なのは「引数xを取ってputs(x)を評価せよ」というモノ(Python流に言うと無名関数、Ruby流に言うとブロック)を渡しているということだと思います。
# Python 2
class Array(list):
def each(self, f):
for i in self:
f(i)
def puts(x):
print x
Array([1, 2, 3, 4, 5]).each(lambda x:
puts(x)
)
逆にRuby 1の方をPython 1に近づけて書くと次のようになるようです。
# Ruby 2
class Array
def my_each
self.each{|x|
yield x
}
end
end
myprint = proc {|x|
puts x
}
[1, 2, 3, 4, 5].my_each(
&myprint
)
Python 1 では関数オブジェクトを引数に渡していましたが、Ruby 2ではProcオブジェクトを渡すようです。
さて、ではPythonでyieldを使うとどうなるでしょう。
# Python 3
class Array(list):
def each(self):
for i in self:
yield i
for i in Array([1, 2, 3, 4, 5]).each():
print i
Rubyと大きく異なる点は、forが必要である点と、eachが引数を取らない点です。Rubyの「yieldを使っているメソッド」は「Procオブジェクトを受け取ってyieldの場所でyieldの後に続いているモノを引数としてそのProcオブジェクトを呼び出す」という働きでしたが、Pythonの「yieldを使っているメソッド」は「なにも受け取らず、ジェネレータを返す」という働きをします。ジェネレータはnextメソッドを持ち、nextが呼ばれるとyieldまでを実行してyieldの後に続いているモノを返り値として返します。ですので、ループを途中で中断することもできますし、中断したループを後で継続することもできます。
# Python 4
class Array(list):
def each(self):
for i in self:
yield i
gen = Array([1, 2, 3, 4, 5]).each()
for i in gen:
print i
if i == 3:
break
print gen.next() # -> 4
Rubyで同じことをやろうとすると、明示的にジェネレータを使って以下のようになります。
# Ruby 3
require 'generator'
g = Generator.new([1, 2, 3, 4, 5])
while g.next?
i = g.next
puts i
if i == 3
break
end
end
puts g.next
Generator.newの引数にはProcオブジェクトを渡せるようですので、無限リストはこんな感じに実装できますね。
# Ruby 4
require 'generator'
def makeList
Generator.new{|g|
i = 0
while true
i += 1
g.yield i
end
}
end
g = makeList
while g.next?
i = g.next
puts i
if i == 3
break
end
end
puts g.next
PythonでRuby 4に相当するコードを書くとこうです。
# Python 5
def makeList():
i = 0
while True:
i += 1
yield i
gen = makeList()
for i in gen:
print i
if i == 3:
break
print gen.next() # -> 4
Rubyでは引数gにProcオブジェクト?を受け取っているようですね。Pythonではジェネレータの時には省略できて、Ruby風のループ抽象(イテレータ?)を行うときには省略せずに引数fに関数オブジェクトを受け取っていました。Rubyではちょうど逆ですね。面白いです。