ruby中的callcc导致无限循环?

时间:2016-11-20 20:29:50

标签: ruby callcc

我试图查看课程的幻灯片。代码应打印"早期工作"然后是"后来的工作"两次(您可以设置后续工作的重复次数)。但我想知道为什么这段代码不起作用,我该如何修改代码呢?从现在开始,代码将生成无限循环的"以后的工作"而不是2(应该是)

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)

1 个答案:

答案 0 :(得分: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中的延续对象的问题。

当continuation重新启动程序时,它可能会回退或保留局部变量的值。我的程序假定entry.call保留了循环计数器。 Matz的Ruby实现保留了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)