コードは動作しません。 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のRuby実装では、ループカウンタはInteger#my_times
に保存されますが、ループカウンタはInteger#times
に巻き戻されます。これが私のプログラムでInteger#times
を使用できない唯一の理由です。
MRIはローカルコードを(Integer#times
のように)Cコードで巻き戻しているようですが、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)
あなたは* *「動作しない」とはどういう意味ですか?あなたのタイトルには無限ループが書いてありますが、どうやってそのマニフェストを見ていますか? – UnholySheep
ええ、コードを実行すると "後の作業"が無限に出力されます。あいまいさを残して申し訳ありません。 –
'entry.call(entry)'だけを 'entry.call()'に変更すると、(実行時エラーで停止しますが)希望の動作が得られます。 – UnholySheep