作者godfat (godfat 真常)
看板Ruby
标题[心得] Generator 与 Continuation
时间Tue Feb 27 00:58:58 2007
Ruby standard library 里有个 Generator, 放在 generator.rb 里。
大抵上是拿来把 internal iterator 转换成 external iterator 用的。
两种 iterator 有什麽差别?如果你熟悉 C++ 或 Java 的话,
他们所使用的就是 external iterator. 顾名思义,这是一种外部的
iterator, 即 iterator 本身跟 container/collection 是分开的。
C++:
typedef std::vector<int> vint;
vint v;
for(vint::iterator i=v.begin(), iend=v.end(); i!=iend; ++i)
std::cout << *i << "\n";
Java:
Vector<Integer> v = new Vector<Integer>();
for(Iterator i=v.iterator(); i.hasNext();)
out.println(i.next());
如果是 internal iterator 的话,就像是 foreach/for in 那样
C++:
BOOST_FOREACH(int i, v){ // 由於这是 macro 做的,不确定能不能省略
std::cout << i << "\n";
}
Java:
for(int i: v)
out.println(i);
也就是说,你只能依序得到元素,但你不能操控会给你的元素是什麽。
external iterator 有比较强大的弹性,internal iterator 使用起来方便。
Ruby 的 each 很明显是 internal iterator,
你把要施行的动作传给 container/collection, 然後由他来替你施行动作。
可以看成是这样:
def each func = nil, &block
# 以下决定使用 block 或传入的 Proc like 物件,写入 func 中
if block_given?
func = block
else
raise TypeError, "#{func} don't respond to :call" unless
func.respond_to? :call
func = func
end
# 以下依次呼叫外部 Proc like 物件,传入被 travel 者
0...self.size.times{|i|
func.call self[i]
}
end
由於 travel 的动作被隐藏起来了,所以使用时方便,不用再去说明 travel 法。
缺点也正是因为 travel 被隐藏起来了,所以如果不是要用预设的 travel 法,
则动他不得。要用非预设的方式,只能使用 external iterator.
Ruby 目前没什麽好的 external iterator, 所以只好使用 Generator 去模拟。
(当然,Array 很好做 external iterator, 但其他的就比较困难。)
用法大概是像这样:
v = [0, 1, 2, 3, 4]
g = Generator.new v
while g.next?
puts g.next
end
Generator 的做法很单纯,他还是使用 each 去呼叫,不过却去「卡住」
travel 的过程。这就是靠 Continuation 去做了。
关於 Continuation, 本板前面有其相关心得,不多重复了。
写成概念有点像这样:
def next
@target.each{|i|
卡住
return i
}
end
每次呼叫 Generator 的 next 时,就放掉「卡住」,於是传回当时的 i,
然後在传回下一个 i 之前,再卡住执行流程。next 可以用这种概念实作出来。
不过 prev 就没办法了。Generator 有提供 rewind, 使 travel 整个重来,
但是没有 prev 这种能够往前走一格的方式…。要有的话,势必不能使用 each.
幸运的是……由於 standard library 的 generator.rb 我看不懂,
看了好一阵子,只得到了点概念,但还是搞不懂他为什麽要把实作变得
这麽复杂 :( 没记错的话,总共有三个 Continuation 物件,又有一个额外的
queue array... 我完全搞不懂他这个 queue 存在的意义。
所以我自己尝试写了一下,以确保我没有误解什麽东西。我只用了两个
Continuation, 没有 queue. 当然,整个概念还是从他的程式码来的,
我只是把我看不懂的部份拿掉,然後用自己的意思解读一次。
用该档案里面的 unit test 测试了一下,每一个测试都通过了,
很搞不懂原本的实作为什麽要弄得这麽复杂…。更何况由於我撰写的
里面少了一个 Continuation 和一个 array, 执行效率应该还比较高。
不知道是不是我疏忽了什麽,不然这样的状况似乎有点诡异。
*
原本我是想详细解说我的实作的,不过 Continuation 我觉得有点复杂,
从写完到现在也经过一段时间了,详细的做法我已经不记得了…。 Orz
更何况此篇已经有点太长了,也差不多是「欲知详情,请待下回分晓」的时候 XD
所以以下就仅附上程式码,以 Ruby license 释出。
p.s. 我觉得 Continuation 太过於复杂(另类 goto),似乎应该少用为妙
class Generator
include Enumerable
def initialize enum = nil, &block
if enum
@block = lambda{|g| enum.each{|i| g.yield i}}
else
@block = block
end
@index = 0
@value = nil
if @con_next = callcc{|c|c}
@block.call self
@con_next.call
end
end
def next
raise EOFError, "no more elements available" if end?
@index += 1
result = @value
@con_yield.call if @con_next = callcc{|c|c}
result
end
def yield value
@value = value
@con_next.call if @con_yield = callcc{|c|c}
end
def next?
return true if @con_yield
return false
end
def end?
!next?
end
def index
@index
end
alias_method :pos, :index
def current
raise EOFError, "no more elements available" unless @value
@value
end
def rewind
initialize &@block
self
end
def each
rewind
yield(self.next) while self.next?
self
end
end
--
In Lisp, you don't just write your program down toward the language,
you also build the language up toward your program.
《Programming Bottom-Up》- Paul Graham 1993
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 220.135.28.18
1F:推 poga:好文我推 03/01 22:37