2016-11-20 62 views
3

我正在尝试审查课程的幻灯片。代码应该打印“早期工作”一次,然后“后来工作”两次(可以设置后续工作的重复次数)。但我想知道为什么这个代码不起作用,我该如何修改代码?由于现在的代码会产生“以后的工作”,而不是2的无限循环(这应该是)ruby​​中的callcc导致无限循环?

require 'continuation' 
def work 
    p "early work" 
    here = callcc {|here| here} 
    p "later work" 
    return here 
end 

def rework(k) 
    entry = work 
    k.times do |i| 
    entry.call(entry) 
    end 
end 

rework(2) 
+0

你说的*“不工作”的意思是*?你的标题提到了一个无限循环,你怎么看到这个清单? – UnholySheep

+0

是的,执行代码将无限地输出“以后的工作”。抱歉,模棱两可。 –

+0

将'entry.call(entry)'改为'entry.call()'应该会给你想要的行为(虽然它仍然会因运行时错误而停止) – UnholySheep

回答

2

的代码不起作用,因为在k.times循环计数器被卡住。每次调用entry.call(entry)将程序回退到callcc返回的时间。于是callcc又回来了,后来的工作再次发生,work又回来了,k.times又开始了。当k.times开始时,它将其循环计数器重置为零。无限循环是因为循环计数器始终为零。

要修复程序,我们必须继续循环,而不是重新启动它。最好的解决办法是使用光纤,但首先,我尝试使用延续。下面是我的机器上工作的版本:

require 'continuation' 
def work 
    p "early work" 
    here = callcc {|here| here} 
    p "later work" 
    return here 
end 

class Integer 
    def my_times 
    i = 0 
    while i < self 
     yield i 
     i += 1 
    end 
    end 
end 

def rework(k) 
    entry = nil 
    k.my_times do |i| 
    if i == 0 
     entry = work 
    else 
     entry.call(entry) 
    end 
    end 
end 

rework(2) 

我通过调用work循环内固定控制流程。当work再次返回时,我不重置循环计数器。

我也定义了我自己的Integer#my_times并且不使用Ruby的Integer#times。如果我将k.my_times的代码更改回k.times,则循环计数器再次卡住。这暴露了Ruby中延续对象的问题。

当继续倒退程序时,它可能回退或保留局部变量的值。我的程序假定entry.call保留了循环计数器。 Matz的红宝石实现保留了Integer#my_times中的循环计数器,但在Integer#times中倒退循环计数器。这是我的程序无法使用Integer#times的唯一原因。

MRI似乎在C代码中重新显示当地人(如Integer#times),但保留Ruby代码中的本地人(如Integer#my_times)。这使得循环计数器和其他当地人混乱。 Ruby不能解决这个问题,但是会对callcc发出警告。 Ruby说,warning: callcc is obsolete; use Fiber instead

下面是一个使用光纤方案:

def work 
    p "early work" 
    here = Fiber.new do 
    while true 
     p "later work" 
     Fiber.yield 
    end 
    end 
    here.resume 
    return here 
end 

def rework(k) 
    entry = nil 
    k.times do |i| 
    if i == 0 
     entry = work 
    else 
     entry.resume 
    end 
    end 
end 

rework(2)