[函數定義] f x y = x - y f(x, y) = x - y [函數定義] f' = f 3 f'(x) = 3 - x f'(2) 的返回值 = f(3, 2)看似平凡不過的推導,但在電腦科學的意義卻是非凡,因為這讓簡化的理論模型,如只接受單一參數的 lambda 得以處理多個參數的函數,當談及 Y Combinator 或者隱喻的遞迴操作,皆從此出發,進而開始璀璨的新頁,不過本文無意深入探索 Functional language,先行省略。
def walk_dir(path_str, fun) path = Pathname.new(path_str) path.children.each do |entry| if entry.directory? fun = walk_dir(entry, fun) elsif entry.file? fun = fun.call(entry) end end return fun end如此一來,可封裝目錄操作,並一致化的處理,所以,若要實做取得目錄下所有檔案的空間,可這麼寫:
def cur_sum(sum) return lambda {|file| (file==nil) ? sum : cur_sum(sum + file.size)} end稍後再傳遞給尋訪的處理,如下:
puts walk_dir(ARGV[0], cur_sum(0)).call(nil)
同理,我們也可透過相近的手法,處理其他目錄層次的操作。不僅目錄尋訪可採用 Currying function 手法,對於資料庫也可,比方說知名的 Java ORM (Object-relational mapping) framework -- [hibernate] 裡頭的 Criteria Queries 就能應用 Currying 的形式如下:SessionFactory.newSession(..). newQuery(…). setParameter(…). setParameter(…)即具有 Curry, Factory Chain 的形式特性。C++ 語言層面並未有 functional programming 的成份,但可透過模擬的方式處理,前述的 C++ STL 與 Loki 就是箇中經典範例,為此,台灣知名的技術作家 [jjhou] 曾多次撰文著述探討極其複雜的實作,集結於「泛型程式設計與 STL」主題,不過本文採用另一種輕巧的方式,並採用簡潔的 C 語言來實做。
typedef int (*intf)(); static int add(int x, int y) { return (x + y); } static int sub(int x, int y) { return (x - y); } int main() { intf ret; ret = curry(add, 1); printf("* (funcall (curry #'+ 1) 1)\n" "%d\n", (*ret)(1)); ret = curry(sub, 2); printf("* (funcall (curry #'- 2) 3)\n" "%d\n", (*ret)(3)); return 0; }筆者定義 intf 這個描述整數型態的 function pointer (容筆者囉唆一下,這當然是指 C 語言的語意),在 main() 中建立一個實體 ret,並試著賦予以下操作:
ret = curry(add, 1); printf("* (funcall (curry #'+ 1) 1)\n" "%d\n", (*ret)(1));這形式就符合前述「上一個 Functor 處理參數的結果產生了下一個 Functor,下一個 Functor 繼續處理下一個參數,產生下一個 Functor」,而且 curry() 的回傳值就是一個新的 function pointer,用以模擬「函數」的動作。那我們該如何實做這樣將 functional programming 與 C 語言橋接的 curry() function 呢?筆者的作法如下:
static char code[18] = { 0x55, /* push %ebp */ 0x89, 0xe5, /* mov %esp, %ebp */ 0xff, 0x75, 0x08, /* pushl 0x8(%ebp) */ 0x6a, 0, /* push $0x0 */ 0xe8, 0, 0, 0, 0, /* call <code+13> */ 0x83, 0xc4, 0x08, /* add $0x8, %esp */ 0xc9, /* leave */ 0xc3 /* ret */ }; int (*curry(intf Func, int arg))() { *(char*)(code + 7) = (char) arg; *(int*)(code + 9) = (int) Func - ((int) code + 9 + 4); return ((intf) code); }顯然,這段程式碼僅能在 IA32 上執行,不過要移植到其他平台,應該是相對單純的動作 (RISC 架構的話,stack 處理可簡化)。我們要掌握 Currying function 的形式與語意,所以筆者先建立一段 shellcode 範本,也就是 char code[],將 Currying 的函數參數在 curry() function 中預先植入,並將函式本身的進入點也寫入 caller。不過,真正的技巧在於 IA32 stack 的處理,兩年前筆者在 [深入淺出 Hello World] Part II 的演講中已有提及,這裡再作簡要複習。
static char code[18] = { 0x55, /* push %ebp */ 0x89, 0xe5, /* mov %esp, %ebp */ ... 0xc9, /* leave */ 0xc3 /* ret */ };省略的部份是 Currying function 的 function body 呼叫動作,而前半段稱為 prologue,後半段則是 epilogue。將 C 語言的程序呼叫對應到 IA32 stack 時,prologue 顧名思義,就是「序幕」的意思,即程序 / function 的開頭,有兩個動作需優先於其他動作前完成:
$ gcc -xc curry.c && ./a.out * (funcall (curry #'+ 1) 1) 2 * (funcall (curry #'- 2) 3) -1當然,這只是一個出發點,實務上可推廣到目錄檔案操作一類的應用。
漂亮的玩法!不愧是 jserv,不過我有個疑問...
stack 裡面的資料直接可以執行嗎?
Intel 下 Protected Mode 不是應該會對 page 有些保護?
至少這樣的 code 在 Windows 上跑我印象中是會 access violation...
好像需要特別配置一個具有可執行屬性的 page 來放
~/work/lab$ linux32 gcc -o curry -xc curry.c
curry.c: 在函数‘curry’中:
curry.c:20: 警告: 将一个指针转换为大小不同的整数
curry.c:20: 警告: 将一个指针转换为大小不同的整数
~/work/lab$ ./curry
段错误
~/work/lab$
Ubuntu 8.04 64bit
@PCMan,
這是 shellcode,利用 x86 的記憶體管理特性:「可讀寫就可執行」。Linux 下,預設 memory page 的保護限制我們對 code context 作寫入的動作 (data 與 code 是獨立的 section),要改變預設的行為,可透過 mprotect(2),範例見前文:
http://blog.linux.org.tw/~jserv/archives/001773.html
@elife,
上文提過,本程式僅能在 IA32 上執行,當然 x86_64 上面執行會失敗
哇咧.. 這算是用 C 實作嗎? 明明就用 assembly/opcode。欺騙我的感情!
:)
『將目前的 stack pointer 設定為 callee (被呼叫的 function) 的 frame pointer,對應的組合語言就是 "movl %ebp, %esp"』
這個地方我覺得有些疑惑(雖然不熟組語),我用C語言寫一個小程式,在 function 的開頭,出現的組合語言是
pushl %ebp
movl %esp, %ebp
和您所說的不太一樣。
FYI,
http://heaven.branda.to/~thinker/GinGin_CGI.py/show_id_doc/326
最近在看lisp, 所以看到這篇文章覺得很有趣
從這篇文章我猜curry的意義:
func1 = curry(op, arg1);
// func1 能夠將op, arg1 保存在他的私有storage上
func1([@op, @arg1,] arg2); // 用的時候就只要pass arg2就好了
用"類"C++的pseudo做看看:
typedef int (*op_func) (int, int);
op_func add(int a, int b) { return a + b; }
op_func sub(int a, int b) { return a - b; }
class func1 {
private:
func1() {};
op_func op;
int para1;
public:
func1(op_func a, int b) : op(a), para(b) {}
int operator()(int para2) { return op(para1, para2); }
};
func1 curry(op_type op, int arg) { return func1(op, arg); }
請問這樣子實作可以嗎?
由 cf 發表於 June 19, 2008 03:19 PMjserv, 「真吸」版完成。
由 Thinker 發表於 June 19, 2008 04:40 PM用macro一樣嗎
#define add(x,y) x+y
#define addp(y) add(1,y)
int main() {
printf("%d",addp(2));
}
哈,我懂了,第一個參數不一定是1
由 gg 發表於 June 19, 2008 11:56 PM發現gtk有
http://library.gnome.org/devel/gobject/stable/gobject-Closures.html