转自 http://blog.csdn.net/hikaliv/article/details/4532980
文 / 李博(光宇广贞)
先以《N1958》提案的开场白,做为本文的开场白吧:
A number of languages provide a way of passing code as arguments without having to define a separate named function. For example, Algol 68 has downward funargs, Lisp has closures, in Smalltalk this is called“code blocks”which can be returned, passed as a parameter and executed later.
贤妻者,糟糠之妻是也;娇娃者,美艳之妻是也。贤者,长相一般,但勤俭持家;娇者,美艳绝伦,却好吃懒作。男人对于贤者,恐多报怨其相貌平平;男人对于娇者,怕对其慵懒是敢怒不敢言。贤妻多劳能干,乃至于上天入地(从 OS 到 APP)无所不能,除了性感没有,男人要什么给什么;娇妻除了风情万种,恐怕只善于满足男人某一两件需要,比如 ML……(我是说 Meta-programming,你以为是什么呢……)
不过话又说回来了,男人要求贤妻变漂亮点儿,难……尽管现在整容技术如此发达,隆个胸什么的倒好办(比如加个 Lambda 什么的),但基因还是那基因,你怎么整?比如与生俱来的大宽骨头架子,想整条儿了就要伤筋动骨啦!男人要求娇妻变勤快点儿,难……敢让老娘干粗活儿?老娘养了二十多年的细皮嫩肉儿啊,你丫的想给老娘毁了!
如是贤妻与娇妻,男人们,挑哪个作老婆?
C++ 为何需要 Lambda
IP 与 FP 就是这样一对儿。IP 语言典型代表如 C++,FP 语言新生有如 F#。我曾在《从 C++ 模板元编程生产质数看 F# 函数式编程思想》中聊到在“模式匹配”的表达上,C++ 处理得不如 F# 优雅。这是 IP 基因带给 C++ 先天之不能。克服先天之不能就要后天与时俱进,C++ 0x 对此可是下了苦功夫,其中一项进步便是引入 Lambda 机制。讨论它之前,先说说“函数”。
数学上的“函数”简而言之是“集到集的映射”,而 IP 语言的“函数”与之有大不同。数学关心返回集是什么;而 IP 根本不在乎有没有值返回,它关心的是过程控制权的返回。
其实,IP 中“函数”本质是“子过程”。
计算机程序的执行过程就是在一个或多个子过程中间来回跳。由于 IP 语言是按照计算机的思维去设计的语言,因此这一执行思想很自然地体现在 C++ 等 IP 语言的编码中,并给其程序设计以很多约束。就拿“函数”来说,比如说,我想设计一个“泛函”。先举一个 FP 语言编码的例子:
图一
图二
第 5 到 8 行定义了一个泛函 Functional。其中 filterFunc 函数用于模式匹配判断流程走向,true 则判给 dealTrueFunc 函数处理,false 则判给 dealFalseFunc 函数处理。在调用时,三个处理函数都用 fun 关键字引导,处理 [1 to 12] 数列。10、11 行将数列中的偶数打印出来;13 到 15 行则是将数列中奇数置零,偶数平方。
这活儿要是交给 C++?……C++ 是无所不能的!为了实现与上述代码一样的功能,我在此给出一种“泛函模板”的写法:
图三
够长的……写成这样,一能保证编译期查错,比如 79 行代入 DealTrue1 就会报错(强类型约束);关键是二能区别两个 Functional 重载(参数表区分)。针对如上两相比较,列出如下几点:
- C++ 须使用模板类 FilterFunc、DealFunc 去封装函数指针方可保证强类型约束。对于允许函数重载的 C++ 来说,强类型约束是多么重要。
- 不同于以数学方式思考的 F#,C++ 做为 IP 语言大哥大级代表,大大方方地允许函数返回 void。这个 void 类型在 C++ 中实在是太个别了,我不得不写两个 Functional 版本的重载,还要使用强类型约束保证重载绑定(Overload Resolution)安全和正确。
- 68 行改用 std::for_each 和 76 行改用 std::transform 的话,需要对 Functional 函数封装,这里我就不这么写了。
其实,这扬扬洒洒 76 行代码里,我只想关注 54 到 60 行那几句有实在意义的。为了实现安全和正确的泛函,周边准备工作做了那么那么那么多……可以想见,若我想添加更多的 DealFunc 函数类型,那么就要准备更多封装其函数指针的类……C++ 贤惠,任劳任怨,但不见得 C++ 程序员都一样任劳任怨……在被计算机式思维洗脑后,程序员们想获得一点人性关怀,程序员想关注他们该关注的事情,就像 68 行和 76 行的 auto 代替 vector<int>::iterator& 一样,我只想用 i,不想管它的出身是否“根儿正苗儿红”,是否“阶级正确”,事儿办成就行,用什么人我不管。用 C++ 能听得懂的方言来说,就是,“强类型约束”设计出来是去约束你的,不是来约束我程序员的。
C++ 贤惠呐,她不想让男人为她太费脑子跟这儿较劲儿了。于是,她跑去向 FP 请教妩媚之道,以便留住更多程序员的心……FP 如是说,“你又来了?这次又要学什么?”
C++:“听 C#、Java 等姐妹们说,Lambda 可以美容养颜,健体强身,调经止带,宜气养血,返老还童,永葆青春……“
FP 寻思着:“你说得这么邪乎?有这么神么?我怎么不知道呢?我的日常用品平凡无奇,说给你们倒当成了宝贝了?”
FP:“你的动作啊,总比人家姐俩儿慢半拍。既已说与她们,今儿个我也说与你,你听去后,当好好消化,善用善行……”
C++ 脸上堆着笑:“得咧!”
FP:“……”,心想,“我怕你脚下使绊子……”
“红楼梦”式的对白先告一段落。不过 Lambda 真的有说的那么邪乎么?我用 Lambda 重写成了如下代码:
图四
果然 FP 是美容专家,教授一招儿 Lambda 就让 C++ 立马由芙蓉变阿娇。首先,39 行和 46 行 Functional 函数的调用方式,已经很像图一 F# 代码一般简捷明快优美了;再者,Functional 重载的定义也方便了许多,不用再做麻烦的准备工作,比如定义封装函数指针的类等等。这些工作已经由 Lambda 表达式替程序员完成了,现在程序员只用关心功能实现的细节,也即我打算怎么折腾 list 列表里面的数字了。现代生活讲究的就是,原料买回家里下锅就能炒;而非为做一个酱牛肉,准备个酱就得花个三五年……
糟糠之妻一觉醒来变二八少女,你还会整日寻思着出去寻花觅柳么?有人对此就很清醒,直言道:“什么嘛!人还那样儿,只不过学会打扮了,卸了妆还那副德性……”哪副德性?图三——卸妆了么——没错,编译器在处理 Lambda 时,就是依照其定义式翻译,创建了类并生成括号算符重载函数后调用之,而且是对每一个 Lambda 表达式都去创建一个。两个一模一样的 Lambda 表达式呢?编译器会一共两份儿全都创建……浪费吧!所以我在图四 36 行用 filterFunc 保存好了 Lambda 表达式对像,这样 39 行和 46 行使用的都是同一个对像。也就是说,程序员在享受 Lambda 带给贤妻青春美艳的同时也要考虑好了背后隐藏的代价。
更不用说,我仍然要生成两个 Functional 版本的重载,用于区别有返回和无返回的情况……
有关 C++ 0x 之 Lambda 更多用法,请参见《C++ 0x 之 Lambda 受 VS 2010 支持》。
随着 C++ 0x 新标准的相继推出实行,IP 语言的几大代表 Java、C#、C++ 等已经纷纷从 FP 讨教了包括 GC、Lambda 在内的许多新元素扩充自己的功能、巩固自己的市场、延续自己的生命。然而 IP 的基因先天决定了 C++ 等语言终生克服不了的痼疾。比如 C++ 等本质上是“子过程”的“函数”是有状态的,这就决定了它永远无法消除副作用(side-effect),这是多线程应用的巨大隐患。再比如 IP 对程序员的洗脑使计算机专业的学生的思维背离了人类应有的数学性和抽象性。太多的人专注于前置自增与后置自增的区别,太多的人专注于常指针与指针常量的区别,太多的人专注于是用 if-else 还是用 switch-case 的问题,等等。这些本不应该是让程序员费心的问题却牵扯了程序员太多的心力。虽然 C++ 引入了 Lambda,但依然把上述的问题留了下来。
参考:
《C++ 0x 之 decltype 和 auto 受 VS 2010 支持》