9/13/2006

Ruby 的 " 延續 " 觀念 : Continuation

試著想想這個程式(出自 Beyound JAVA 範例)

1 def con_loop

2 for i in 1..5 do
3 puts i
4 callcc { |a| return a } if i == 2
5 puts '#'
6 end
7 return nil
8 end

9 puts
'Before loop call'
10 cont = con_loop()
11 puts 'After loop call'
12 cont.call if cont
13 puts 'After continuation call'
想想這個程式會怎麼做?

這是這個程式的結果,很出人意料之外吧
Before loop call
1
#
2
After loop call
#
3
#
4
#
5
#
After loop call
After continuation call
為什麼 After loop call 執行了兩次?為什麼 1 2 跟 3 4 5 中間夾了一個 After Call。這就講到一個有趣的物件 Continuation,他是一個物件,裡面記載著程式運作的狀態
沒錯,他記載了程式運行的狀態!!!
好像遊戲打到一半,媽媽叫你去吃飯,你只好心不甘情不願的去存檔。可是吃完飯,load 剛剛存的檔,你的角色的經驗值都還在,寶物都不會不見一樣。Continuation 就是那個遊戲的紀錄檔

現在可以討論這個程式了。

  1. i 進入loop裡面 ,puts 1 ,puts 2。
  2. 當 i = 2 時,下一行也就是第四行,因為 if i == 2 這個敘述的緣故 媽媽叫你吃飯了)
  3. 會被強迫跳出這個 loop ( return a ) (只好關機)
  4. callcc { |a| return a } 他會 return 一個 continuation 物件,continuation 裡面的內容就是你遊戲的紀錄檔,他會在 10行裡面,存到 cont 這個 Object 裡面 (存檔等下再來玩),所以程式會 puts 1 , 2 之後,就進入第11行的 puts 'After loop call',這也是第一次的 After loop call。
  5. 第12行,他會檢查 cont 這個物件有沒有值 (檢查存檔在不在)
  6. 發現到有值,就執行 Continuation 物件的 call method (呼叫存檔)
  7. 他會到 loop function 裡面 ,剛剛 return continuation 物件的地方。最重要的是他會紀錄 i 這個 tmp variable 是 2,因為他本來就會紀錄程式狀態。
  8. 執行第五行的 puts '#',這也是為啥,結果裡面第一個 After loop call 後面是 #。
  9. 他會繼續的進行 loop ,從 i =3 ~ 5
  10. 最後執行結束,他會在第7行 return null 值,交給 cont 物件。
  11. 之後他就會執行第二次的 After loop call。

這個機制怎麼玩呢?

我們可以想到,程式執行到一半,有個 interrupt,他出去執行一下 error handler ,然後回來繼續處理剛剛的事情,當然我們當然可以用其他方式做到類似的事情,不過這個作法的確提供很方便的方式。

根據 Wikipedia 的講法,Lisp ,Ruby ,Smalltalk 都有提供類似的作法(果然 Ruby 是 Perl + SmallTalk + Lisp ),但是我比較好奇 C 也有這種作法?

改天我會提到利用這個觀念,產生的延續性伺服器的想法。

1 則留言:

malkhut 提到...

真的蠻………難消化的…是說callcc回傳的物件被call的那一瞬間,程式會回到當初callcc被呼叫的那一點嗎?