先週末のRuby勉強会＠関西で、Rubyにおけるcallccの使い方について発表させていただきました。

継続の説明については「なんでも継続」がよく参照されるんだけど、 ちょっと説明がボトムアップすぎると思うので(僕も最初に読んだときは全然分からなかった)、「callccで何ができるか」という応用面から攻める 構成にしてみました。

最初は「継続かわいいよ継続」「それをすてるなんてとんでもない」と思ってたんだけど、 いろいろ調べてるうちになんでcallccが嫌われるのかが理解できてしまった。callccはかわいいけど、非常に手のかかる奴らしい。 しかも、面白い利用例はいっぱいあるけど実用的な例があんまりないんだよね^^;。

callccが無くなるとRubyの「かっこよさ」が1さがる。そのかわりに「がんじょうさ」が1あがる。さあ、あなたはどちらを望みますか？

以下テキスト版。

要旨

callccって何？ RPGのセーブ・ロードみたいに、プログラム中でセーブポイントを作って「そこからやり直し」したりできるものだよ

callccは凄い！ 利用例をいくつか紹介します

callccは危ない！ プログラムを変なところから「再開」できてしまうので、Ruby本体や拡張ライブラリの実装が面倒になるよ Ruby 1.9では、callccの機能の一部を実現する「Fiber」という機能がテストされてるよ



callccって何？

キーワード：継続、Continuation、callcc

ドラクエ的に言うと 王様と話すとセーブできる ドラゴンのこうげき！ 89のダメージ まつもとはしんでしまった 王様「まつもとよ、しんでしまうとはふがいない」 →セーブしたところからやりなおし



callccはセーブポイントに似ている callccでセーブ、cc.callでロード



callcc{}の返り値： cc.call(arg)で飛んできたときはarg そうでない時(最初の一回)はブロックの返り値



まとめ セーブ ＝ callcc{|cc| … } # ccがセーブポイント ロード ＝ cc.call callccの「次の処理」から再開される



callccは凄い！

(1) 3重ループを一発で脱出

callcc{|cc| for i in (0..10) for j in (0..10) for k in (0..10) if i==j && j==k cc.call end end end end }

それcatchでできるよ

catch(:escape){ for i in (0..10) for j in (0..10) for k in (0..10) if i==j && j==k throw :escape end end end end }

google code searchではループ脱出にcallccを使ってる例が 結構あったけど、throw/catchでできるよ

(2) メソッドを「少しずつ」実行する

ゲームのイベント処理

def event king.say(“おお#{@name}よ、しんでしまうとはふがいない”) wait_ok king.say(“そなたにもういちどきかいをあたえよう”) wait_ok king.say(“では ゆけ #{@name}よ！”) end

wait_okをどのように実装すればいい？

def wait_ok loop do input.poll break if input[”ok”] #これは他の処理が sleep 1 #止まってしまうのでダメ end end

普通に書くと非常にめんどう(´・ω・｀)

def event(input) case @step when 0 king.say “おお#{@name}！ しんでしまうとはふがいない” @step += 1 when 1 @step += 1 if input[“ok”] when 2 king.say “そなたに もういちど きかいを あたえよう” @step += 1 when 3 @step += 1 if input[“ok”] when 4 king.say “では ゆけ #{@name}よ！” end end

callccを使うと…

def event king.say(“おお#{@name}よ、しんでしまうとはふがいない”) wait_ok king.say(“そなたにもういちどきかいをあたえよう”) wait_ok king.say(“では ゆけ #{@name}よ！”) end

メソッドを「少しずつ」実行できる

wait_okが呼ばれたたら、セーブしてreturn

次回の呼び出しはセーブしたところから再開

(3) 全探索を簡単に

あるマンションに５人の男が住んでいる bakerは5Fではない cooperは1Fではない millerはcooperより上の階にいる smithとfletcherは1つ隣の階にいる…



require "amb" A = Amb.new baker = A.choose(1, 2, 3, 4, 5) cooper = A.choose(1, 2, 3, 4, 5) fletcher = A.choose(1, 2, 3, 4, 5) miller = A.choose(1, 2, 3, 4, 5) smith = A.choose(1, 2, 3, 4, 5) A.assert([baker, cooper, fletcher, miller, smith].uniq.length == 5) A.assert(baker != 5) A.assert(cooper != 1) A.assert(fletcher != 1 && fletcher != 5) A.assert(miller > cooper) A.assert((smith - fletcher).abs != 1) A.assert((fletcher - cooper).abs != 1) p [baker, cooper, fletcher, miller, smith]

(4) eachを「少しずつ」回す

C++風のイテレータ

requrie ‘generator’ (標準添付)

g = Generator.new([1,2,3]) while g.next? p g.next end

(5) ppp

変数名も出力してくれるpメソッド http://www.rubyist.net/~rubikitch/computer/ppp/

いちいち p "a=#{a}" みたいに書かなくていいので便利

irb(main):109:0> a = 1 1 irb(main):112:0> ppp :a a = 1

実装は超複雑

Ruby界の3大黒魔術が夢の競演！ eval系 (eval, *_eval) フック系 (*_missing, set_trace_func) callcc



こんなもん誰が考えたんだ Binding.of_caller として Rails (ActiveSupport)で導入されたのが初出？ 1.8.5以降だと動かない…。



callccは危ない！

なぜ危ない？ (Rubyの) バグの原因になりやすい 油断するとRubyが落ちる



例：fooというブロックを取るメソッドがCで実装されていたとする。

foo do puts "hoge" puts "moge" end

static VALUE rb_foo(VALUE ary1) { int *p = malloc(...); … rb_yield(); … free(*p); }

ブロック内でcallccを使うと、rb_yield(); 以下が2回実行されてしまい、メモリを2重開放しようとしてRubyが落ちる。

これを避けるには、Ruby開発陣や拡張ライブラリの作者が常にcallccの存在を気にしてコーディングしなければならない。

まとめ

callccを使うと スパゲッティコードが簡単に書ける！

callccはとても強力 処理の流れを自在に操れる

でも他人に読めないコードになりやすい 見えないところに隠そう



callccは楽しい 思いもよらないことができる

でもRubyインタプリタのバグ要因になり易い 将来無くなるかも



おまけ(Fiberについて)

callccの代表的な使い方は

(A) 処理の中断/再開 (generator, wait_ok)

(B) 処理のやり直し (amb, ppp)

の2通りが挙げられる。

callccが危険なのは(B)ができてしまうからだ。 じゃあ(A)の機能だけなら残してもいいかも？ということで、Ruby 1.9ではFiberという機能が検討されている。

FiberはThreadみたいなものだけど、Threadのように自動的に並列実行はされず、Thread#yieldで明示的に移動先を切り替えてやる。 軽量スレッドとも呼ばれるらしい。(Thread＝糸、Fiber＝繊維)

Fiberを使うと、callccの機能のうち(A)を簡単に実現できる。

Ruby1.9は継続と“Fiber”をサポート - @IT …と書かれてるけど、実際はまだ決まってない。あり得る選択肢は：

Fiberだけが残る callccもFiberも残る callccもFiberも残らない callccだけが残る

(↑一応、ありそうな順に並べてみた)