2016-11-20 112 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)