這是 sicp 5.2 assemble 的程式, 難倒我也。
g.scm 是我簡化的版本。用來說明這個 recusive + callback 的想法。如果你想轉腦袋瓜看這程式, 我建議你先試著用半個小時看看這程式, 如果看懂了就不用看這篇, 看不懂再來看, 可能才有點體會我在寫什麼。
一個 recursive 程式, 可能難不倒你, 如果再加上 callback function 那真是難倒人, 我的腦袋轉了好幾轉, 終於搞懂了。
extract-labes 接受兩個參數, 一個 list, 一個 procedure, 這個 procedure 接受兩個參數, 而這個 procedure 還會呼叫自己, 你很少這麼寫程式吧? g.scm L15 就是要傳進去的 procedure receive, L18 則是在傳入的 receive 再呼叫傳進去的 receive, 你應該糊塗了, 沒關係, 我也是, 直接看程式碼。
我們來 "人工" 追蹤整個呼叫流程, 我把每一層會傳給 extract-labels 的參數, 還有 next-inst, 以及傳入的 receive procedure 都描述出來。
extract-labels 1 '( 2 (assign a (reg b)) 3 (assign b (reg t)) 4 gcd-done 5 ) 99 next-inst: (car text) = test-b (
第二層 L1~5 是第一次傳給 extract-labels 的參數一 (一個 list, 就是 text)
第二層 L11~16 是第一次傳給 extract-labels 的參數二 (一個 procedure 接受兩個參數, 就是 receive - receive-2)
L99 的 next-inst 很重要, 所以也一起列了出來, 最後追蹤執行流程時, 會用到這個。
extract-labels '( (assign b (reg t)) gcd-done ) next-inst: (car text) = (assign a (reg b)) (
extract-labels '( gcd-done ) next-inst: (car text) = (assign b (reg t)) (
extract-labels '( ) next-inst: (car text) = gcd-done (
由於這次傳給 extract-labels 的是 '(), 所以 receive 要發動了, 重點是 receive 是如何發動? 這是 recursive 的發動, 很精彩哦!
receive 是 receive-5 next-inst 是 gcd-done insts 是 '() labels 是 '()
所以變成這樣
(receive insts (cons (make-label-entry next-inst insts) labels)) => (receive '() (cons (make-label-entry 'gcd-done '()) '())) => (receive '() (cons (gcd-done) '())) => (receive '() ((gcd-done)) )
這時候的 receive 是誰呢?
receive 是 receive-4 next-inst 是 (car text) = (assign b (reg t)) insts 是 '() labels 是 ((gcd-done))
(receive (cons (make-instruction next-inst) insts) labels) => (receive (cons (make-instruction (assign b (reg t)) ) '() ) ((gcd-done)) ) => (receive (cons ((assign b (reg t))) '() ) ((gcd-done)) ) => (receive (((assign b (reg t))) ) ((gcd-done)) )
一樣要問: 這時候的 receive 是誰呢?
receive 是 receive-3 next-inst 是 next-inst: (assign a (reg b)) insts 是 (((assign b (reg t)))) labels 是 ((gcd-done))
再來一次:
(receive (cons (make-instruction next-inst) insts) labels)
=>
(receive (cons (make-instruction (assign a (reg b)) ) (((assign b (reg t)))) ) ((gcd-done)) )
=>
L1 (receive (cons ((assign a (reg b)) ) (((assign b (reg t)))) ) ((gcd-done)) )
1 ]=> (cons '((assign a (reg b)) ) '(((assign b (reg t)))) ) ;Value 11: (((assign a (reg b))) ((assign b (reg t))))
=>
(receive (((assign a (reg b))) ((assign b (reg t)))) ((gcd-done)) )
N 問: 這時候的 receive 是誰呢?
receive 是 receive-2 next-inst 是 next-inst: test-b insts 是 (((assign a (reg b))) ((assign b (reg t)))) labels 是 ((gcd-done))
再次推導出:
(receive insts (cons (make-label-entry next-inst insts) labels)) => (receive insts (cons (make-label-entry test-b (((assign a (reg b))) ((assign b (reg t)))) ) labels)) => (receive insts (cons (make-label-entry 'test-b '(((assign a (reg b))) ((assign b (reg t)))) ) labels)) => (receive insts (cons (test-b ((assign a (reg b))) ((assign b (reg t)))) labels) ) => (receive insts (cons (test-b ((assign a (reg b))) ((assign b (reg t)))) ((gcd-done)) ) ) => (receive insts (cons '(test-b ((assign a (reg b))) ((assign b (reg t)))) '((gcd-done)) ) ) => (receive insts ((test-b ((assign a (reg b))) ((assign b (reg t)))) (gcd-done)) ) => (receive (((assign a (reg b))) ((assign b (reg t)))) ((test-b ((assign a (reg b))) ((assign b (reg t)))) (gcd-done)) )
(receive (((assign a (reg b))) ((assign b (reg t)))) <- insts ((test-b ((assign a (reg b))) ((assign b (reg t)))) (gcd-done)) <- label )
N+1 問: 這時候的 receive 是誰呢? receive 是 receive-1; next-inst 是 ... 沒有
next-inst 了。 receive-1 很單純把 insts, labels 印出來, 不必再辛苦的轉腦袋了。
insts/labels: +++ (((assign a (reg b))) ((assign b (reg t)))) ( (test-b ((assign a (reg b))) ((assign b (reg t))) ) (gcd-done)) ++++++
呼! 終於追完了, 這下你可知道 gdb 的辛苦了。這個技巧有什麼好處值得我們這樣傷腦筋呢? 這樣的寫法可以不必回傳一個 (insts, labels) 的資料結構, 就把傳入的 '(test-b (assign a (reg b)) (assign b (reg t)) gcd-done) list 從中取出 insts, labels, sicp 5.2 另外提供了傳回 (insts, labels) 的寫法, 有興趣的朋友可以參考。
recursive_callback.cpp 這是我用 c++ 寫的版本, 使用了 virtual function, function object 來達成這樣的技巧。
這個範例把 string str="x51ca2yz"; 數字和英文字母分開印出來, 這麼簡單的工作用這種技巧處理變成超級複雜。
1 // ref: http://www.scheme.com/tspl4/further.html#./further:h4 2 #include <string> 3 #include <iostream> 4 5 // use function object, virtual function. 6 7 using namespace std; 8 9 //typedef void (*Recv)(string &a, string &b); 10 class Recv 11 { 12 public: 13 virtual void operator() (const string &num, const string &str)=0; 14 }; 15 16 class Recv1:public Recv 17 { 18 public: 19 virtual void operator() (const string &num, const string &str) 20 { 21 cout << "num: " << num << endl; 22 cout << "str: " << str << endl; 23 } 24 private: 25 char next_char_; 26 Recv *recv_; 27 }; 28 29 class Recv2:public Recv 30 { 31 public: 32 Recv2(Recv *recv, char nc) 33 { 34 next_char_ = nc; 35 recv_ = recv; 36 } 37 virtual void operator() (const string &num, const string &str) 38 { 39 string tmp_str; 40 tmp_str.push_back(next_char_); 41 #ifdef MORE_INFO 42 std::cout << "next_char_:" << next_char_ << endl; 43 std::cout << "num: " << num << endl; 44 std::cout << "str: " << str << endl; 45 #endif 46 47 if ('0' <= next_char_ && next_char_ <= '9') 48 { 49 string new_str = num + tmp_str; 50 (*recv_)(new_str, str); 51 } 52 else // non num 53 { 54 string new_str = str + tmp_str; 55 (*recv_)(num, new_str); 56 } 57 } 58 private: 59 char next_char_; 60 Recv *recv_; 61 }; 62 63 void extract(const string &s, Recv *receive) 64 { 65 if (s.length() == 0) 66 { 67 string e1, e2; 68 (*receive)(e1, e2); 69 } 70 else 71 { 72 Recv2 recv2(receive, s[0]); 73 extract(s.substr(1), &recv2); 74 } 75 } 76 77 void assemble(string &str) 78 { 79 Recv1 recv1; 80 extract(str, &recv1); 81 } 82 83 int main(int argc, char *argv[]) 84 { 85 string str="x51ca2yz"; 86 87 cout << str << endl; 88 assemble(str); 89 return 0; 90 }
t.cpp 則是另外一個版本, 用了 c++11 lambda, std::function, function template 來做到類似的事情。我本來以為只需要 lambda 和 function pointer 的宣告就可以搞定,
我雖然搞懂了, 但是要我以這種思維來寫程式, 呃 ... 我功力還不夠。自從學了 c++11 後, 連自己都看不懂自己在寫什麼了。
1 // ref: http://kheresy.wordpress.com/2010/05/27/c0x%EF%BC%9Alambda-expression/ 2 #include <vector> 3 #include <algorithm> 4 #include <iostream> 5 #include <string> 6 #include <typeinfo> 7 #include<functional> 8 9 using namespace std; 10 11 typedef void (*Recv)(const string &n, const string &s); 12 13 //template<typename Func> 14 //void extract(const string &str, Func receive) 15 void extract(const string &str, std::function<void(const string&, const string&)> receive) 16 { 17 if (str.length() == 0) 18 { 19 string e1, e2; 20 receive(e1, e2); 21 } 22 else 23 { 24 // ref: http://www.cnblogs.com/ttss/p/4100917.html 25 // std::function<int(int ,int)>func=add; 26 // <int(int,int)>是实例化模板参数,表示返回值为int,函数参数为2个,(int,int), 27 // 即int(*pfunc)(int ,int )类型的函数 28 29 extract 30 ( 31 str.substr(1), 32 [&](const string &n, const string &s) 33 { 34 char next_ch = str[0]; 35 string tmp_str; 36 37 tmp_str.push_back(next_ch); 38 39 #ifdef MORE_INFO 40 cout << "n: " << n << endl; 41 cout << "s: " << s << endl; 42 cout << "next_ch: " << next_ch << endl; 43 #endif 44 45 if ('0' <= next_ch && next_ch <= '9') 46 { 47 string new_str = n + tmp_str; 48 #ifdef MORE_INFO 49 cout << "new_str: " << new_str << endl; 50 cout << "===========" << endl; 51 #endif 52 receive(new_str, s); 53 } 54 else 55 { 56 string new_str = s + tmp_str; 57 #ifdef MORE_INFO 58 cout << "new_str: " << new_str << endl; 59 cout << "===========" << endl; 60 #endif 61 receive(n, new_str); 62 } 63 } 64 65 66 ); 67 } 68 } 69 70 void assemble(string &str) 71 { 72 extract(str, 73 [] (const string &n, const string &s) 74 { 75 cout << n << endl; 76 cout << s << endl; 77 } 78 ); 79 } 80 81 int main(int argc, char *argv[]) 82 { 83 auto func = [&] (const string &n, const string &s) 84 { 85 cout << n << endl; 86 cout << s << endl; 87 }; 88 89 string str="x51ca2yz"; 90 cout << str << endl; 91 //extract(str, func); 92 assemble(str); 93 return 0; 94 }
據說這種手法叫作 CPS, 請參考 http://www.scheme.com/tspl4/further.html#./further:h4
為了畫出那個漂亮的 callback, 還有兩個 c++ 版本, 這篇讓我元氣大傷。
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。