数年間ずっとPython一筋だったけど、Rubyに手を出している。
Pythonの一番のウリはiterator/generatorだと勝手に思う。
そこで、Rubyでiteratorと呼ばれているモノは何なのか、自分なりにやってみた。
勿論RubyにはRubyならではの書きやすいやり方、考え方があるはずだけど。
渡す側
ブロックを渡すだけなら{...}とdo ... endを使っていればよい
(1..3).map {|x| x * 2} # => [2, 4, 6]
ブロック渡さずにnext()とかしたい場合はEnumeratorオブジェクトに変換する
a = (1..3).to_enum
a.next() # => 1
渡すブロックを部品化したい場合
はじめに思いつくこと
def f1(x)
x * 2
end
(1..3).map {|x| f1(x)} # => [2, 4, 6]
lambdaやProcを使うと、ブロックをオブジェクトにする
f1 = lambda {|x| x * 2} # f1とf2は同じ意味。f1とf3はスコープの考えがちょっと違う(割愛)
f2 = ->(x) {x * 2}
f3 = Proc.new {|x| x * 2}
(1..3).map &f1 # => [2, 4, 6]
(1..3).map &f2 # => [2, 4, 6]
(1..3).map &f3 # => [2, 4, 6]
渡される側
yieldキーワードを使う
def itr_func
(1..3).each {|x| yield x}
end
l = []
itr_func {|x| l << x}
l # => [1, 2, 3]
enumeratorオブジェクトにする
無限リストとかに使う
enum = Enumerator.new do |yielder|
yielder.yield 1
yielder.yield 2
yielder.yield 3
end
enum.next() # => 1
enum.next() # => 2
enum.next() # => 3
また、既存のイテレータを変更するのにも使う
def itr_func
(1..3).each {|x| yield x}
end
enum = Enumerator.new do |yielder|
Object.itr_func {|x| yielder.yield x}
end
enum.next() # => 1
enum.next() # => 2
続けて応用
ブロックをメソッドチェインみたいに使えるの?
コンテナを渡すのは素直にできる
enum = ->(x){x * 2}
(1..3).map(&enum).map(&enum) # => [4, 8, 12]
コンテナじゃなくて任意のenumerator(Pythonで言うgenerator)を繋げたい。
つまり、あるイテレータにブロックを渡すと、そのブロックに処理を委譲して、その結果をまた移譲するようなenumeratorを返して欲しい。
class Chainer
attr_reader :enum
def initialize(enum)
@enum = enum
end
def chain(&block)
next_enum = Enumerator.new do |yielder|
loop do
yielder.yield block.call(enum.next)
end
end
Chainer.new(next_enum)
end
end
enum = Enumerator.new do |yielder|
yielder.yield 1
yielder.yield 2
yielder.yield 3
end
proder = ->(x) {x * 2}
Chainer.new(enum).chain(&proder).chain(&proder).enum.to_a # => [4, 8, 12]
無理やり作ってみたけど、きっともっといい方法があるはず。
誰か教えてください。
これとは違う例だけど、ピッケル本には色々やりたいならEnumerator自体を拡張すればいいじゃんとか書いてあった。
さすがにそれはキモい。
外側からくるんであげるのが最もお行儀の良い方法だと思うの。
結論
- Pythonのgenerator最高
- Rubyだと配列とかハッシュとか作りまくって渡さないと発狂しそうになる。速度とかキニスンナ。