2006/08/11

Lambda Expressions in C++0x

Lambda expressions 指的是在使用地點即時定義、建立 function objects(or functions)的式子。在 functional programming languages 裡面,函式是第一級(first-class)物件,且大量使用 higher-order functions(接收其他函式作為引數的函式),此類能力直是天經地義。例如 Common Lisp 這麼寫:

(lambda (x) (+ x 1))  ; 定義一個 function,接收一個引數 x,回返 x + 1 的值

Haskell 這麼寫

\x -> x + 1  -- 定義一個 function,接收一個引數 x,回返 x + 1 的值

Ruby 少用迴圈而大量使用 blocks,意義與 lambda expressions 幾乎相同:

(1..5).each {|x| puts x + 1}  # 將 1 到 5 間的每個數傳入隨附於呼叫的 block 之內,
                              # 對於每個傳入的 x,印出 x + 1 的值                            

如果要產生第一級(first-class)的 blocks,也可使用 Kernel.lambda 將 block 轉換為 Proc object:

lambda {|x| x + 1}  # 產出一個 Proc object,接收一個引數 x,回返 x + 1 的值

JavaScript 的語法也很直覺:

function (x) { return x + 1; }  // 定義一個 function,接收一個引數 x,回返 x + 1 的值

Java 雖然語法囉唆且必須和 interface(or (abstract) classes)一同使用,但也透過 anonymous inner classes 支援此事:

interface Int2Int {
  int apply(int x);
}

// ...

new Int2Int() {
  int apply(int x) {
    return x + 1;
  }
}  // 至此生成一個實作 Int2Int interface 的 object

C++ 面對示例裡這種簡單的 function objects 仍遊刃有餘:

bind2nd(plus<int>(), 1)  // 造出一個 function object,接收 x 回返 x + 1

但相較於上列語言,這個奠基於 library 的機制顯然不夠一般化,意義也不那麼明顯。Boost Lambda Library 的效果不錯:

_1 + 1  // 造出一個 function object,接收一個數(作為第一引數),回返那個數加 1

但內部所用的 expression template 技巧需要較長編譯時間,而且有所侷限。(詳見 proposal N1968 Sec. 1.1, p.2)因此,C++0x 很可能內建 lambda expressions,這將大大鼓勵 standard library algorithms 的使用。

目前(如 proposal N1968 所述)提議的語法像這樣:

<> (int x) { return x + 1; }

這將使一個 class 被定義出來,具有一個 public operator(),並在那個 lambda expression 出現的地方即時建立一個該 class 的 object。上述 lambda expression 並未指明造出的 function object 的 call operator 回返型別,因為 C++0x 另外有個 proposal 是 autodecltype(ref. proposal N1705)。前者我已多次提及,而後者可用來推導算式的型別。上面那個 lambda expression 就等價於:

<> (int x) -> decltype(x + 1) { return x + 1; }

Lambda expressions(or more specifically, closures)最有意思的特性在於能夠存取所處語境(context)的狀態(state,i.e. 變數的值),例如:

vector<int> v;

// fill v with some values...

int n;
cin >> n;

for_each(v.begin(), v.end(), <> (int& x) -> void { x += n; } );

(關於「for_each 的 "non-mutating algorithms" 身分與此處用法相衝突」的討論請見《for_each??》此文與我的回覆。)上示的那個 lambda expression 會在產出的 function object 之內以 by-value 方式儲存一份 n 的複本。如果想要的是 by-reference 語意,可以這麼寫:

ofstream fout;

// open fout...

for_each(v.begin(), v.end(), <> (int x) -> void extern(fout) { fout << x << endl; } );

如果這個 lambda expression 所造出的 function object 被喚起時,fout 已經退離作用域(gets out of scope),將導致 undefined behavior。(很好想像,如同提領使用 dangling pointer 一般。)

相較於 Ruby、Java 兩個非函數式語言(和函數式語言還有什麼好比 XD),C++ 對 "function as a first-class object" 的支援最為漂亮,主要關鍵在於重載 operator() 的能力。而 lambda expression 加入後,程式員使用 function objects 的意願必然會大為提高,對於 functional programming in C++ 絕對有相當大的正面影響。(目前 C++ functional programming 的勢力相當弱,類似當年 generic programming(或 template 的應用)被視為附屬於 data abstraction 之下;不過 C++ functional programming 的勢力應該沒辦法成長到像 generic programming 那般規模,畢竟 C++ 本質上是 imperative language。)關於 lambda expressions 的其餘細節請參閱相關 proposals,或等到 2009 年(根據《Effective C++, 3/e》)閱讀《The C++ Programming Language, 4/e》或《C++ Primer, 5/e》:)。

--
Lambda expressions 在我的 C++0x wishlist 上排名很前面,因為實在太好用了 XD。

--
本文同時展示四種註釋(comment)形式 XD。

Anonymous 遊客8/11/2006 4:22 pm 說:

好像語法還是滿難看的, C++ 這樣下去很難說會真的比較好 -_-''

 
Blogger Josh Ko8/11/2006 4:55 pm 說:

要拚語法乾淨漂亮,怎麼拚得贏 Ruby 呢 XD。Java 的 API 介面那麼 verbose,還不是活得好好的,語法醜沒差啦 XD。

C++0x 的成敗關鍵,應該還是在 concept system 吧。Lambda expression 這種規模的東西,終究是 syntactic sugar 而已(但我沒說 syntactic sugar 不好不重要喔 XD)。

 
Blogger yen38/13/2006 1:31 pm 說:

_1 + 1 這種東西熊熊看起來,是不太能了解
我只能說,boost 真的很厲害吧
即時定義是個好主意,但是是否會讓程式碼較為難讀呢?

 

<< 回到主頁