函数式编程另类指南


blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第1页 共20页 2007/1/29 20:03 欢迎您的来访!blog中文翻译是团体blog,翻译 互联网、媒体、中国相关文章。 秉承开放的姿态,坚信分享的乐趣,让更多精华思 想激发我们的思考。 blog中文翻译 搜索 首页 全部文章 作者聚合 FAQ 论坛 翻译列表 在线翻译 什么是语义网络?——让你的数据更加有用 网络视频, 再接再厉 函数式编程另类指南 November 15th, 2006 原文链接:Functional Programming For The Rest of Us 原文作者:Vyacheslav Akhmechet 翻译:lihaitao (电邮: lihaitao在gmail.com) 校对:刘凯清 程序员拖沓成性,每天到了办公室后,泡咖啡,检查邮箱,阅读 RSS feed,到技 术站点查阅最新的文章,在编程论坛的相关版面浏览公共讨论,并一次次地刷新 以免漏掉一条信息。然后是午饭,回来后盯了IDE没几分钟,就再次检查邮箱,倒 咖啡。最后在不知不觉中,结束了一天。 不平凡的事是每隔一段时间会跳出一些很有挑战性的文章。如果没错,这些天你 至少发现了一篇这类文章——很难快速通读它们,于是就将之束之高阁,直到突 然你发现自己已经有了一个长长的链接列表和一个装满了PDF文件的目录,然后 你梦想着到一个人迹罕至的森林里的小木屋苦读一年以期赶上,要是每天清晨你 沿着那里的林中小溪散步时会有人带来食物和带走垃圾就更好了。 虽然我对你的列表一无所知,但我的列表却是一大堆关于函数式编程的文章。而 这些基本上是最难阅读的了。它们用枯燥的学院派语言写成,即使“在华尔街行业 浸淫十年的专家(veterans)”也不能理解函数式编程(也写作FP)都在探讨些什 么。如果你去问花旗集团(Citi Group)或德意志银行(Deutsche Bank)的项目 经理[1],为什么选择了 JMS 而不 Erlang,他们可能回答不能在产业级的应用中 使用学院派语言。问题是,一些最为复杂的,有着最严格需求的系统却是用函数 式编程元素写成。有些说法不能让人信服。 的确,关于函数式编程的文章和论文难于理解,但他们本来不必这么晦涩。这一 知识隔阂的形成完全是历史原因。函数式编程的概念本身并不困难。这篇文章可 以作为“简易的函数式编程导引”。是一座从我们命令式(imperative)的思维模式 到函数式编程的桥梁。去取杯咖啡回来继续读下去吧。可能你的同事很快就会开 始取笑你对函数式编程发表的观点了。 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第2页 共20页 2007/1/29 20:03 那么什么是函数式编程呢?它怎么产生?它可以被掌握吗(Is it edible)?如果它 真如其倡导者所言,为什么没有在行业中得到更广泛的使用?为什么好像只有那 些拿着博士学位的人才使用它?最要紧的是,为什么它就 TMD 这么难学?这些 closure, continuation, currying,惰性求值和无副作用等等究竟是些什么东西?没 有大学参与的项目怎么使用它?为什么它看上去这么诡异于和我们命令式思想友 好,圣洁和亲近的一切的一切?我们将于不久扫清这些疑问。首先让我来解释形 成实际生活和学界文章之间巨大隔阂的缘起,简单得像一次公园的散步。 信步游园 启动时间机器,我们散步在两千多年以前的一个被遗忘了太久的春季明媚的日 子,那是公元前380年。雅典城墙外的橄榄树树荫里,柏拉图和一个英俊的奴隶小 男孩朝着学院走去。“天气真好”,“饮食不错”,然后话题开始转向哲思。 “瞧那两个学生,”为了使问题更容易理解,柏拉图仔细地挑选着用词, “你认为谁更高呢?” 小男孩看着那两个人站着的水漕说,“他们差不多一样高”。 柏拉图说:“你的差不多一样是什么意思?”。“我在这里看他们是一样 高的,不过我肯定如果走近些就会看出他们高度的差别。” 柏拉图笑了,他正把这个孩子带到正确的方向。“那么你是说,我们这 个世界没有完全的等同了?” 小男孩想了一会儿回答,“对,我不这样认为,任何事物总有一些区 别,即使我们看不到它。” 这句话非常到位!“那么如果这世上没有完全的相等,你又是如何理解 ‘完全’相等这个概念的呢?” 小男孩迷惑得说:“我不知道。”最初尝试着理解数学的本源(nature) 时也会产生这种疑惑。 柏拉图暗示这个世上的万物都只是一个对完美的近似。他还认识到我们即使没有 接触到完美但依然可以理解这一概念。所以他得出结论,完美的数学形式只能存 在于另一个世界,我们通过和那个世界的某种联系在一定程度上知晓他们。很明 显我们不能看到完美的圆,但我们可以理解什么是完美的圆并用数学公式将它表 达出来。那么,什么是数学?为什么宇宙可以用数学定理描述?数学可以描述宇 宙中的所有现象吗?[2] 数学哲学是一个很复杂的课题。像大多数哲学学科一样它更倾向于提出问题而不 是给出解答。这些意见中很多都循回绕转于一个事实,即数学实际上是一个谜 语:我们设置了一系列基本的不冲突的原理和一些可以施加于这些原理的操作规 则,然后我们就能堆砌这些规则以形成更复杂的规则。数学家把这种方法叫做“形 式系统”或“演算”。如果愿意,我们可以很快写出一个关于 Tetris(译者注:一种通 常被称为俄罗斯方块的游戏)的形式系统。实际上,工作中的 Tetris 实现就是一 个形式系统,只是被指定使用了个不常见的表现形式。 人马座的那个生物文明也许不能理解我们的 Tetris 和圆的范式,因为可能他们唯 一的感知输入是气味香橙的橘子。他们也许永远不会发现 Tetris 范式,但很可能 会有一个圆的范式。我们也可能将无法阅读它,因为我们的嗅觉没有那么复杂, 可是一旦我们理解(pass)了那一范式的表示形式(通过这种传感器和标准解码技术 来理解这种语言),其底层的概念就可被任何智能文明所理解。 有趣的是如果从来没有智能文明存在,Tetris 和圆的范式仍然严密合理,只是没有 人注定将会发现他们。如果产生了一种智能文明,他就会发现一些形式系统来帮 助描述宇宙的规律。但他还是不大可能发现 Tetris 因为宇宙中再没有和它相似的 事物。在现实世界中这类无用的形式系统或迷题的例子数不胜数,Tetris 只是其中 的一个典型。我们甚至不能确定自然数是否是对客观世界的完整近似,至少我们 可以简单的想像一个很大的数它不能用宇宙中任何东西描述,因为它以近乎无 穷。 历史一瞥[3] blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第3页 共20页 2007/1/29 20:03 再次启动时间机器,这一次的旅行近了很多,我们回到 1930 年代。大萧条正在蹂 躏着那个或新或就的时代。空前的经济下挫影响着几乎所有阶层的家庭生活,只 有少数人还能够保持着饥谨危机前的安逸。一些人就如此幸运地位列其中,我们 关心的是普林斯顿大学的数学家们。 采用了歌特式风格设计建造的新办公室给普林斯顿罩上天堂般的幸福光环,来自 世界各地的逻辑学家被邀请到普林斯顿建设一个新的学部。虽然彼时的美国民众 已难能弄到一餐的面包,普林斯顿的条件则是可以在高高的穹顶下,精致雕凿的 木质墙饰边上整日的品茶讨论或款款慢步于楼外的林荫之中。 阿隆左·丘奇就是一个在这种近于奢侈的环境中生活着的数学家。他在普林斯顿获 得本科学位后被邀留在研究生院继续攻读。阿隆左认为那里的建筑实属浮华,所 以他很少一边喝茶一边与人讨论数学,他也不喜欢到林中散步。阿隆左是一个孤 独者:因为只有一个人时他才能以最高的效率工作。虽然如此,他仍与一些普林 斯顿人保持的定期的联系,其中包括阿兰·图灵,约翰·冯·诺依曼,和 kurt Grodel。 这四个人都对形式系统很感兴趣,而不太留意现实世界,以便致力于解决抽象的 数学难题。他们的难题有些共同之处:都是探索关于计算的问题。如果我们有了 无限计算能力的机器,哪些问题可以被解决?我们可以使他们自动地得以解决 吗?是否还是有些问题无法解决,为什么?不同设计的各种机器是否具有相同的 计算能力? 通过和其它人的合作,阿隆左·丘奇提出了一个被称为 lambda 演算的形式系统。 这个系统本质上是一种虚拟的机器的编程语言,他的基础是一些以函数为参数和 返回值的函数。函数用希腊字母 lambda 标识,这个形式系统因此得名[4]。利用 这一形式系统,阿隆左就可以对上述诸多问题推理并给出结论性的答案。 独立于阿隆左,阿兰·图灵也在进行着相似的工作,他提出了一个不同的形式系统 (现在被称为图灵机),并使用这一系统独立得给出了和阿隆左相似的结论。后 来被证明图灵机和 lambda 演算能力等同。 我们的故事本可以到此结束,我会就此歇笔,而你也将浏览到下一个页面,如果 第二次世界大战没有在那时打响。整个世界笼罩在战争的火光和硝烟之中,美国 陆军和海军前所未有的大量使用炮弹,为了改进炮弹的精确度,部队组织了大批 的科学家持续地计算微分方程以解出弹道发射轨迹。渐渐意识到这个任务用人力 手工完成太耗精力后,人们开始着手开发各种设备来攻克这个难关。第一个解出 了弹道轨迹的机器是 IBM 制造的 Mark I —— 它重达5吨,有75万个组件,每秒可 以完成三次操作。 竞争当然没有就此结束,1949年,EDVAC(Electronic Discrete Variable Automatic Computer,爱达瓦克)被推出并获得了极大的成功。这是对冯·诺依曼 架构的第一个实践实例,实际上也是图灵机的第一个现实实现。那一年好运与阿 隆左·丘奇无缘。 直到1950年代将尽,一位 MIT 的教授John McCarthy(也是普林斯顿毕业生)对 阿隆左·丘奇的工作产生了兴趣。1958年,他公开了表处理语言 Lisp。Lisp 是对 阿隆左·丘奇的 lambda 演算的实现但同时它工作在冯·诺依曼计算机上!很多计算 机科学家认识到了 Lisp 的表达能力。1973年,MIT人工智能实验室的一组程序员 开发了被称为Lisp机器的硬件-阿隆左 lambda 演算的硬件实现! 函数式编程 函数式编程是对阿隆左·丘奇理论的实践应用。但也并非全部 lambda 演算都被应 用到了实践中,因为 lambda 演算不是被设计为在物理局限下工作的。因此,象 面向对象的编程一样,函数式编程是一系列理念,而不是严格的教条。现在有很 多种函数式编程语言,他们中的大多数以不同方式完成不同任务。在本文中我将 就最广泛使用的源自函数式编程的思想作一解释,并将用Java语言举例。(的确, 你可以用Java写出函数式的程序如果你有显著的受虐倾向)。在下面的小节中, blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第4页 共20页 2007/1/29 20:03 我将会把Java作为一种函数式语言,并对其稍加修改使它成为一种可用的函数式 语言。现在开始吧。 lambda 演算被设计用来探询关于计算的问题,所以函数式编程主要处理计算,并 惊人地用函数来完成这一过程。函数是函数式编程的基本单位,函数几乎被用于 一切,包括最简单的计算,甚至变量都由计算取代。在函数式编程中,变量只是 表达式的别名(这样我们就不必把所有东西打在一行里)。变量是不能更改的, 所有变量只能被赋值一次。用 Java 的术语来说,这意味着所有单一变量都被声明 为 final(或 C++ 的 const)。在函数式编程中没有非 final 的变量。 final int i = 5; final int j = i + 3; 因为函数式编程中所有变量都是 final 的,所以可以提出这样两个有趣的表述:没 有必要总是写出关键字 final,没有必要把变量再称为变量。那么现在我们对Java 作出两个修改:在我们的函数式 Java 中所有变量默认都是 final的,我们将变量 (variable)称为符号(symbol)。 就此你也许会质疑,用我们新创造的语言还能写出有些复杂度的程序吗?如果每 个符号都是不可变更(non-mutalbe)的,那么就无法改变任何状态!其实事实并非 完全如此。在阿隆左研究其 lambda 演算时,他并不想将某个状态维护一段时间 以期未来对其进行修改。他关注的是对数据的操作(也通常被称为”演算体 caculating stuff”)。既然已被证明lambda演算与图灵机等价,它可以完成所有命 令式编程语言能够完成的任务。那么,我们怎么才能做到呢? 答案是函数式程序能保存状态,只是它并非通过变量而是使用函数来保存状态。 状态保存在函数的参数中,保存在堆栈上。如果你要保存某个状态一段时间并时 不时地对其进行一些修改,可以写个递归函数。举个例子,我们写个函数来翻转 Java 的字符串。记住,我们声明的每个变量默认都是 final 的。[5] String reverse(String arg) { if(arg.length == 0) { return arg; } else { return reverse(arg.substring(1, arg.length)) + arg.substring(0,1); }} 这个函数很慢因为它不断地调用自己[6],它还也是个嗜内存魔因为要持续分配对 象。不过它的确是在用函数式风格。你可能会问,怎么有人会这样写程序?好 的,我这就慢慢讲来: 函数式编程的优点 你可能会认为我根本无法对上面那个畸形的函数给出个合理的解释。我开始学习 函数式编程时就是这么认为的。不过我是错了。有很好的理由使用这种风格,当 然其中一些属主观因素。例如,函数式程序被认为更容易阅读。因为每个街区的 孩子都知道,是否容易理解在旁观者的眼中,所以我将略去这些主观方面的理 由。幸运的是,还有很多的客观理由。 单元测试 因为函数式编程的每一个符号都是 final 的,没有函数产生过副作用。因为从未在 某个地方修改过值,也没有函数修改过在其作用域之外的量并被其他函数使用 (如类成员或全局变量)。这意味着函数求值的结果只是其返回值,而惟一影响 其返回值的就是函数的参数。 这是单元测试者的梦中仙境(wet dream)。对被测试程序中的每个函数,你只需在 意其参数,而不必考虑函数调用顺序,不用谨慎地设置外部状态。所有要做的就 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第5页 共20页 2007/1/29 20:03 是传递代表了边际情况的参数。如果程序中的每个函数都通过了单元测试,你就 对这个软件的质量有了相当的自信。而命令式编程就不能这样乐观了,在 Java 或 C++ 中只检查函数的返回值还不够——我们还必须验证这个函数可能修改了的外 部状态。 调试 如果一个函数式程序不如你期望地运行,调试也是轻而易举。因为函数式程序的 bug 不依赖于执行前与其无关的代码路径,你遇到的问题就总是可以再现。在命 令式程序中,bug 时隐时现,因为在那里函数的功能依赖与其他函数的副作用, 你可能会在和 bug 的产生无关的方向探寻很久,毫无收获。函数式程序就不是这 样——如果一个函数的结果是错误的,那么无论之前你还执行过什么,这个函数 总是返回相同的错误结果。 一旦你将那个问题再现出来,寻其根源将毫不费力,甚至会让你开心。中断那个 程序的执行然后检查堆栈,和命令式编程一样,栈里每一次函数调用的参数都呈 现在你眼前。但是在命令式程序中只有这些参数还不够,函数还依赖于成员变 量,全局变量和类的状态(这反过来也依赖着这许多情况)。函数式程序里函数 只依赖于它的参数,而那些信息就在你注视的目光下!还有,在命令式程序里, 只检查一个函数的返回值不能够让你确信这个函数已经正常工作了,你还要去查 看那个函数作用域外数十个对象的状态来确认。对函数式程序,你要做的所有事 就是查看其返回值! 沿着堆栈检查函数的参数和返回值,只要发现一个不尽合理的结果就进入那个函 数然后一步步跟踪下去,重复这一个过程,直到它让你发现了 bug 的生成点。 并行 函数式程序无需任何修改即可并行执行。不用担心死锁和临界区,因为你从未用 锁!函数式程序里没有任何数据被同一线程修改两次,更不用说两个不同的线程 了。这意味着可以不假思索地简单增加线程而不会引发折磨着并行应用程序的传 统问题。 事实既然如此,为什么并不是所有人都在需要高度并行作业的应用中采用函数式 程序?嗯,他们正在这样做。爱立信公司设计了一种叫作 Erlang 的函数式语言并 将它使用在需要极高抗错性和可扩展性的电信交换机上。还有很多人也发现了 Erlang 的优势并开始使用它。我们谈论的是电信通信控制系统,这与设计华尔街 的典型系统相比对可靠性和可升级性要求高了得多。实际上,Erlang 系统并不可 靠和易扩展,Java 才是。Erlang 系统只是坚如磐石。 关于并行的故事还没有就此停止,即使你的程序本身就是单线程的,那么函数式 程序的编译器仍然可以优化它使其运行于多个CPU上。请看下面这段代码: String s1 = somewhatLongOperation1(); String s2 = somewhatLongOperation2(); String s3 = concatenate(s1, s2); 在函数编程语言中,编译器会分析代码,辨认出潜在耗时的创建字符串s1和s2的 函数,然后并行地运行它们。这在命令式语言中是不可能的,因为在那里,每个 函数都有可能修改了函数作用域以外的状态并且其后续的函数又会依赖这些修 改。在函数式语言里,自动分析函数并找出适合并行执行的候选函数简单的像自 动进行的函数内联化!在这个意义上,函数式风格的程序是“不会过时的技术 (future proof)”(即使不喜欢用行业术语,但这回要破例一次)。硬件厂商已经无法让 CPU运行得更快了,于是他们增加了处理器核心的速度并因并行而获得了四倍的 速度提升。当然他们也顺便忘记提及我们的多花的钱只是用在了解决平行问题的 软件上了。一小部分的命令式软件和 100% 的函数式软件都可以直接并行运行于 这些机器上。 代码热部署 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第6页 共20页 2007/1/29 20:03 过去要在 Windows上安装更新,重启计算机是难免的,而且还不只一次,即使是 安装了一个新版的媒体播放器。Windows XP 大大改进了这一状态,但仍不理想 (我今天工作时运行了Windows Update,现在一个烦人的图标总是显示在托盘里 除非我重启一次机器)。Unix系统一直以来以更好的模式运行,安装更新时只需 停止系统相关的组件,而不是整个操作系统。即使如此,对一个大规模的服务器 应用这还是不能令人满意的。电信系统必须100%的时间运行,因为如果在系统更 新时紧急拨号失效,就可能造成生命的损失。华尔街的公司也没有理由必须在周 末停止服务以安装更新。 理想的情况是完全不停止系统任何组件来更新相关的代码。在命令式的世界里这 是不可能的。考虑运行时上载一个Java类并重载一个新的定义,那么所有这个类 的实例都将不可用,因为它们被保存的状态丢失了。我们可以着手写些繁琐的版 本控制代码来解决这个问题,然后将这个类的所有实例序列化,再销毁这些实 例,继而用这个类新的定义来重新创建这些实例,然后载入先前被序列化的数据 并希望载入代码可以恰到地将这些数据移植到新的实例。在此之上,每次更新都 要重新手动编写这些用来移植的代码,而且要相当谨慎地防止破坏对象间的相互 关系。理论简单,但实践可不容易。 对函数式的程序,所有的状态即传递给函数的参数都被保存在了堆栈上,这使的 热部署轻而易举!实际上,所有我们需要做的就是对工作中的代码和新版本的代 码做一个差异比较,然后部署新代码。其他的工作将由一个语言工具自动完成! 如果你认为这是个科幻故事,请再思考一下。多年来 Erlang工程师一直更新着他 们的运转着的系统,而无需中断它。 机器辅助的推理和优化 函数式语言的一个有趣的属性就是他们可以用数学方式推理。因为一种函数式语 言只是一个形式系统的实现,所有在纸上完成的运算都可以应用于用这种语言书 写的程序。编译器可以用数学理论将转换一段代码转换为等价的但却更高效的代 码[7]。多年来关系数据库一直在进行着这类优化。没有理由不能把这一技术应用 到常规软件上。 另外,还能使用这些技术来证明部分程序的正确,甚至可能创建工具来分析代码 并为单元测试自动生成边界用例!对稳固的系统这种功能没有价值,但如果你要 设计心房脉冲产生器 (pace maker)或空中交通控制系统,这种工具就不可或缺。 如果你编写的应用程序不是产业的核心任务,这类工具也是你强于竞争对手的杀 手锏。 高阶函数 我记得自己在了解了上面列出的种种优点后曾想:“那都是非常好的特性,可是如 果我不得不用天生就不健全的语言编程,把一切变量声明为 final 产生的代码将是垃圾一堆。” 这其实是误解。在如Java 这般的命令式语言环 境里,将所有变量声明为 final 没有用,但是在函数式语言里不是这样。函数式语 言提供了不同的抽象工具它会使你忘记你曾经习惯于修改变量。高阶函数就是这 样一种工具。 函数式语言中的函数不同于 Java 或 C 中的函数,而是一个超集——它有着 Java 函数拥有的所有功能,但还有更多。创建函数的方式和 C 中相似: int add(int i, int j) { return i + j; } 这意味着有些东西和同样的 C 代码有区别。现在扩展我们的 Java 编译器使其支 持这种记法。当我们输入上述代码后编译器会把它转换成下面的Java代码(别忘 了,所有东西都是 final 的): class add_function_t { int add(int i, int j) { blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第7页 共20页 2007/1/29 20:03 return i + j; } } add_function_t add = new add_function_t(); 这里的符号 add 并不是一个函数。这是一个有一个成员函数的很小的类。我们现 在可以把 add 作为函数参数放入我们的代码中。还可以把它赋给另一个符号。我 们在运行时创建的 add_function_t 的实例如果不再被使用就将会被垃圾回收掉。 这些使得函数成为第一级的对象无异于整数或字符串。(作为参数)操作函数的 函数被称为高阶函数。别让这个术语吓着你,这和 Java 的 class 操作其它 class (把它们作为参数)没有什么区别。我们本可以把它们称为“高阶类”但没有人注意 到这个,因为 Java 背后没有一个强大的学术社区。 那么怎样,何时应该使用高阶函数呢?我很高兴你这样问。如果你不曾考虑类的 层次,就可能写出了一整团堆砌的代码块。当你发现其中一些行的代码重复出 现,就把他们提取成函数(幸运的是这些依然可以在学校里学到)。如果你发现 在那个函数里一些逻辑动作根据情况有变,就把他提取成高阶函数。糊涂了?下 面是一个来自我工作的实例:假如我的一些 Java 代码接受一条信息,用多种方式 处理它然后转发到其他服务器。 class MessageHandler { void handleMessage(Message msg) { // … msg.setClientCode(”ABCD_123″); // … sendMessage(msg); } // … } 现在假设要更改这个系统,现在我们要把信息转发到两个服务器而不是一个。除 了客户端的代码一切都像刚才一样——第二个服务器希望这是另一种格式。怎么 处理这种情况?我们可以检查信息的目的地并相应修改客户端代码的格式,如 下: class MessageHandler { void handleMessage(Message msg) { // … if(msg.getDestination().equals(”server1″) { msg.setClientCode(”ABCD_123″); } else { msg.setClientCode(”123_ABC”); } // … sendMessage(msg); } // … } 然而这不是可扩展的方法,如果加入了更多的服务器,这个函数将线性增长,更 新它会成为我的梦魇。面向对象的方法是把MessageHandler作为基类,在导出类 中专业化客户代码操作: blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第8页 共20页 2007/1/29 20:03 abstract class MessageHandler { void handleMessage(Message msg) { // … msg.setClientCode(getClientCode()); // … sendMessage(msg); } abstract String getClientCode(); // … } class MessageHandlerOne extends MessageHandler { String getClientCode() { return “ABCD_123″; } } class MessageHandlerTwo extends MessageHandler { String getClientCode() { return “123_ABCD”; } } 现在就可以对每一个服务器实例化一个适合的类。添加服务器的操作变得容易维 护了。但对于这么一个简单的修改仍然要添加大量的代码。为了支持不同的客户 代码我们创建了两个新的类型!现在我们用高阶函数完成同样的功能: class MessageHandler { void handleMessage(Message msg, Function getClientCode) { // … Message msg1 = msg.setClientCode(getClientCode()); // … sendMessage(msg1); } // … } String getClientCodeOne() { return “ABCD_123″; } String getClientCodeTwo() { return “123_ABCD”; } MessageHandler handler = new MessageHandler(); handler.handleMessage(someMsg, getClientCodeOne); 没有创建新的类型和新的class层次,只是传入合适的函数作为参数,完成了面向 对象方式同样的功能,同时还有一些额外的优点。没有使自己囿于类的层次之 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第9页 共20页 2007/1/29 20:03 中:可以在运行时传入函数并在任何时候以更高的粒度更少的代码修改他们。编 译器高效地为我们生成了面向对象的“粘合”代码!除此之外,我们还获得了所有函 数式编程的其他好处。当然函数式语言提供的抽象不只这些,高阶函数只是一个 开始: currying 我认识的大多数人都读过“四人帮”的那本设计模式,任何自重的程序员都会告诉你 那本书是语言中立的(agnostic),模式在软件工程中是通用的,和使用的语言无 关。这个说法颇为高贵,故而不幸的是,有违现实。 函数式编程极具表达能力。在函数式语言中,语言既已达此高度,设计模式就不 再是必需,最终你将设计模式彻底消除而以概念编程。适配器(Adapter)模式就是 这样的一个例子。(究竟适配器和 Facade 模式区别在哪里?可能有些人需要在这 里再多费些篇章)。一旦语言有了叫作 currying 的技术,这一模式就可以被消除。 currying. 适配器模式最有名的是被应用在 Java 的“默认”抽象单元——class 上。在函数式 编程里,模式被应用到函数。模式带有一个接口并将它转换成另一个对他人有用 的接口。这有一个适配器模式的例子: int pow(int i, int j); int square(int i) { return pow(i, 2); } 上面的代码把一个整数幂运算接口转换成为了一个平方接口。在学术文章里,这 个雕虫小技被叫作currying(得名于逻辑学家Haskell Curry,他曾将相关的数学理论形式化 )。因为在函数式编程中函数(反之如class) 被作为参数来回传递,currying 很频繁地被用来把函数调整为更适宜的接口。因 为函数的接口是他的参数,使用 currying 可以减少参数的数目(如上例所示)。 函数式语言内建了这一技术。不用手动地创建一个包装了原函数的函数,函数式 语言可以为你代劳。同样地,扩展我们的语言,让他支持这个技术: square = int pow(int i, 2); 这将为我们自动创建出一个有一个参数的函数 square。他把第二个参数设置为 2 再调用函数 pow。这行代码会被编译为如下的 Java 代码: class square_function_t { int square(int i) { return pow(i, 2); } } square_function_t square = new square_function_t(); 正如你所见,通过简单地创建一个对原函数的包装,在函数式编程中,这就是 currying —— 快速简易创建包装的捷径。把精力集中在你的业务上,让编译器为 你写出必要的代码!什么时候使用 currying?这很简单,任何时候你想要使用适 配器模式(包装)时。 惰性求值 惰性(或延迟)求值这一技术可能会变得非常有趣一旦我们采纳了函数式哲学。 在讨论并行时已经见过下面的代码片断: blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第10页 共20页 2007/1/29 20:03 String s1 = somewhatLongOperation1(); String s2 = somewhatLongOperation2(); String s3 = concatenate(s1, s2); 在一个命令式语言中求值顺序是确定的,因为每个函数都有可能会变更或依赖于 外部状态,所以就必须有序的执行这些函数:首先是 somewhatLongOperation1,然后 somewhatLongOperation2,最后 concatenate,在函数式语言里就不尽然了。 前面提到只要确保没有函数修改或依赖于全局变量,somewhatLongOperation1 和 somewhatLongOperation2 可以被并行执行。但是如果我们不想同时运行这两 个函数,还有必要保证有序的执行他们呢?答案是不。我们只在其他函数依赖于 s1和s2时才需要执行这两个函数。我们甚至在concatenate调用之前都不必执行他 们——可以把他们的求值延迟到concatenate函数内实际用到他们的位置。如果用 一个带有条件分支的函数替换concatenate并且只用了两个参数中的一个,另一个 参数就永远没有必要被求值。在 Haskell 语言中,不确保一切都(完全)按顺序 执行,因为 Haskell 只在必要时才会对其求值。 惰性求值优点众多,但缺点也不少。我们会在这里讨论它的优点而在下一节中解 释其缺点。 优化 惰性求值有客观的优化潜力。惰性编译器看函数式代码就像数学家面对的代数表 达式————可以注销一部分而完全不去运行它,重新调整代码段以求更高的效 率,甚至重整代码以降低出错,所有确定性优化(guaranteeing optimizations)不会 破坏代码。这是严格用形式原语描述程序的巨大优势————代码固守着数学定 律并可以数学的方式进行推理。 抽象控制结构 惰性求值提供了更高一级的抽象,它使得不可能的事情得以实现。例如,考虑实 现如下的控制结构: unless(stock.isEuropean()) { sendToSEC(stock); } 我们希望只在祖先不是欧洲人时才执行sendToSEC。如何实现 unless?如果没有 惰性求值,我们需要某种形式的宏(macro)系统,但 Haskell 这样的语言不需要它。把他实现为一个函数即可: void unless(boolean condition, List code) { if(!condition) code; } 注意如果条件为真代码将不被执行。我们不能在一个严格(strict)的语言中再现 这种求值,因为 unless 调用之前会先对参数进行求值。 无穷(infinite)数据结构 惰性求值允许定义无穷数据结构,对严格语言来说实现这个要复杂的多。考虑一 个 Fibonacci 数列,显然我们无法在有限的时间内计算出或在有限的内存里保存 一个无穷列表。在严格语言如 Java 中,只能定义一个能返回 Fibonacci 数列中特 定成员的 Fibonacci 函数,在 Haskell 中,我们对其进一步抽象并定义一个关于 Fibonacci 数的无穷列表,因为作为一 个惰性的语言,只有列表中实际被用到的部分才会被求值。这使得可以抽象出很 多问题并从一个更高的层次重新审视他们。(例如,我们可以在一个无穷列表上 使用表处理函数)。 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第11页 共20页 2007/1/29 20:03 缺点 当然从来不存在免费的午餐。惰性求值有很多的缺点,主要就在于,懒。有很多 现实世界的问题需要严格求值。例如考虑下例: System.out.println(”Please enter your name: “); System.in.readLine(); 在惰性求值的语言里,不能保证第一行会在第二行之前执行!那么我们就不能进 行输入输出操作,不能有意义地使用本地(native)接口(因为他们相互依赖其副 作用必须被有序的调用),从而与整个世界隔离。如果引入允许特定执行顺序的 原语又将失去数学地推理代码的诸多好处(为此将葬送函数式编程与其相关的所 有优点)。幸运的是,并非丧失了一切,数学家为此探索并开发出了许多技巧来 保证在一定函数设置下(function setting)代码以一特定的顺序执行。这样我们就赢 得了两个世界。这些技术包括 continuation, monad 和 uniqueness typing (一致型别)。我只会在本文中解释continuation,把 monad 和 uniqueness typing 留到将来的文章中。有趣的是,除了确保函数求值顺序, continuation 在 很多别的情况下也很有用。这点等一会儿就会提到。 Continuations Continuations 对于程序设计的意义,就像《达芬奇密码》对人类历史的意义:即 对人类最大秘密的惊人揭示。也许不是,但他在概念上的突破性至少和揭示了负 数的平方根意义等同。 我们在学习函数时,只是学到了一半的事实,因为我们基于一个错误的假定:函数 只能将结果返回到它的调用函数。在这个意思上continuation 是广义的函数。函数 不必要返回到其调用函数而可以返回到程序的任何地方。我们把”continuation” 作 为参数传给一个函数,它指定了这个函数返回的位置。这个描述可能听起来更加 复杂。看一下下面的代码: int i = add(5, 10); int j = square(i); 函数 add 在其被调用的位置将结果 15 赋给了 i,接下来 i 的值被用来调用 square。注意所有的惰性求值编译器都不能调整这几行代码因为第二行依赖着第 一行的成功求值。下面用 continuation 风格又称 CPS (Continuation Programming Style) 来重写这段代码,这里函数 add 会将结果返回到 square 而 不是原来的调用函数。 int j = add(5, 10, square); 这个例子中 add 有了另一个参数 —— 一个 add 必须在它求值结束时用其返回值 调用的函数。这里 square 是 add 的一个 continuation。这两种情况下,j 都将等 于 255。 这就是强制使惰性语言有序地求值两个表达式的第一个技巧。考虑下面这个(熟 悉的)IO代码: System.out.println(”Please enter your name: “); System.in.readLine(); 这两行不相依赖所以编译器会自由的重新调整他们的执行顺序。然而,如果我们 用 CPS 来重写这段代码,就会有一个依赖,编译器会因此而强制对这两行代码有 序执行! System.out.println(”Please enter your name: “, System.in.readLine); 这里 println 需要用自己的返回结果作为参数去调用 readLine 并将 readLine 返回 值作为自己的返回值。这样就能确保这两行被有序执行而且 readLine 一定被执行 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第12页 共20页 2007/1/29 20:03 (因为整个计算期望最后的结果为结果)。Java 的 println 返回 void 但如果它返 回的是一个抽象值(readLine所期待的),我们就解决了这个问题!当然这样的 链接函数调用很快就会使代码难以读懂,不过这个可以避免。比如我们可以给语 言添加些语法甜点(syntactic sugar)就可以简单的按顺序输入表达式,然后由编译 器自动为我们链接这些函数调用。这样就可以如愿地使用期望的求值顺序并保留 一切函数式编程的好处(包括数学地对我们程序进行推理的能力)!如果还是有 迷惑,记住函数是只有一个成员的类的实例。重写上述代码使得 println 和 readLine 成为类的实例,这样就对一切都清楚了。 如果我在此结束本节,那将仅仅涉及到 continuation 最浅显的应用。用 CPS 重写 整个程序,那里所有的函数都增加一个额外的 continuation 参数并把函数结果传 给它。也可以通过简单地把函数当作 continuation 函数(总是返回到调用者的函 数)的特殊实例来将程序转为 CPS 风格。这种转换很容易被自动化(事实上,许 多编译器就是这么做的)。 一旦我们将一个程序转为了CPS,那么很明显每个指令都将有些 continuation, 这 是一个该指令在执行结束时会用其执行结果调用的函数,通常的程序中,这是一 个它要返回的地址。从上面的例子中随便举个例子,比如 add(5, 10)。在用CPS 风格写的程序里,add 的continuation很明显——这是一个 add 在其执行结束时会 调用的函数。那么如果在非CPS的程序里,它是什么呢?当然我们可以把程序转 为 CPS ,但有这个必要吗? 其实没有必要。仔细看一下我们的 CPS 转换过程。如果尝试为它写一个编译器, 然后经过长期的思考后,你意识到这个 CPS 的版本根本不需要栈!没有函数会以 传统的意义“返回”,它只是用结果调用了另一个函数。我们无需在调用时将函数参 数压栈再于调用结束时弹出栈,而只是简单的把他们保存在一大块内存中,然后 使用跳转指令。不再需要原来的参数——他们不会再次被用到,因为没有函数会 返回! 所以,用 CPS 风格写成的程序没有堆栈,但每个函数却有一个额外的参数可被调 用。不是 CPS 风格的程序没有可以被调用的这个参数,但却有栈。栈中存放着什 么?只是参数和一个指向函数返回地址的指针。你看到光了吗?栈中只是放着 continuation 的信息! 栈中指向返回指令的指针本质上和 CPS 程序里将被调用的 函数是等价的。如果你想探究 add(5,10) 的 continuation,只要简单地检查它在堆 栈的执行点! 这的确很简单。continuation 和栈上指向返回地址的指针是等价的,只是 continuation 是被显式传递,所以不必和函数被调用点是同一位置。如果还记得 continuation 就是一个函数,并且在我们的语言里,函数被编译为一个类的实例, 你就会理解指向栈中返回指令的指针实际就是传递给 continuation 的参数,因为 我们的函数(就像一个类的实例)只是一个指针。这意味着给定程序中任意时间 和任意位置,你都可以去请求一个当前的 continuation (current continuation)(它 就是当前的栈的信息)。 好的,这样我们就知道了什么是 current continuation。他有什么意义?一旦我们 得到了当前的 continuation 并将它保存在某处,我们就最终将程序当前的状态保 存了下来——及时地冷冻下来。这就像操作系统将其置为休眠状态。一个 continuation 对象里保存了在我们获得它的地方重新启动程序的必要信息。操作系 统在每次发生线程间的上下文切换时也是如此。唯一的区别是它保留着全部控 制。请求一个continuation 对象(在Scheme里,可以调用 call-with-current-continuation 函数)后,你就会获得一个包括了当前 continuation 的对象——堆栈(或者在CPS情况下则是下一个要调用的函数)。可以把这个对 象保存在一个变量(或者是磁盘)里。当你用这 continuation “重启”程序时,就会 转回到处你取得这个对象的那个状态。这就象切换回一个被挂起的线程或唤醒休 眠着的操作系统,区别是用 continuation,你可以多次地重复这一过程。当操作系 统被唤醒时,休眠信息就被销毁了。但如果那些信息没有被销毁,你也就可以一 次次地将它唤醒到同一点,就象重返过去一样。有了 continuation 你就有了这个 控制力! blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第13页 共20页 2007/1/29 20:03 Continuation 应该在什么情况下使用呢?通常在尝试模拟一个本质上是无状态的 应用时可以简化你的任务。Continuation 很适合在Web应用程序中使用。微软公 司的 ASP.NET 技术极尽苦心地模拟状态以便你在开发 Web 应用时少费周折。可 如果 C# 支持了continuation,ASP.NET 的复杂度就可以减半——你只需要保存 一个 continuation,当用户下次发出 web 请求时重启它即可。对程序员来说, web 应用程序将不再有中断——程序只是简单的从下一行重启!利用 continuation 这一抽象解决问题真是令人难以置信的便利。考虑到越来越多的胖客 户端应用程序正在向服务器端转移,将来 continuation 也会变得越来越重要。 模式匹配 模式匹配不是什么新的创新的特性。事实上,它和函数式编程的关系不大。把产 生模式匹配归因于函数式编程的唯一的原因是函数式语言一度提供了模式匹配, 然而现在的命令式语言还做不到。 让我们用一个例子深入了解一下模式匹配。这是一个Java的Fibonacci函数: int fib(int n) { if(n == 0) return 1; if(n == 1) return 1; return fib(n - 2) + fib(n - 1); } 让我们从Java衍生出的语言来支持模式匹配: int fib(0) { return 1; } int fib(1) { return 1; } int fib(int n) { return fib(n - 2) + fib(n - 1); } 两者有什么区别?编译器为我们实现了分支。这有什么大不了?的确没什么。有 人注意到很多函数包括了复杂的 swith 语句(尤其是在函数式程序中)所以认为 这种抽象形式很好。我们把一个函数定义分离成多个,然后把模式置于参数中 (有点象重载)。当这个函数被调用时,编译器使其比较参数和其运行时的定义 然后选择其中正确的一个。这一般是通过选择可选的最特定的定义来完成。例 如,int fib(int n) 可以以 n 等于 1 被调用,但是实际上 fib(n) 没有被调用,因为 fib(1) 更加特定。 模式匹配通常要比我这个例子复杂,比如,高级模式匹配系统可以让我们这样 做: int f(int n < 10) { ... } int f(int n) { ... } 模式匹配什么时候适用?情况太多了!每当你有一个嵌套着 if 的复杂的数据结 构,这时就可以用模式匹配以更少的代码完成得更好。一个很好的例子闪现在我 脑海,这就是所有 Win32 平台都提供了的标准的 WinProc 函数(即使它通常被抽 象了)。通常模式匹配系统能检测集合也可以应付简单的值。例如,当传给函数 一个数组后,就可以找出所有首元素为 1 第三个元素大于 3 的所有数组。 模式匹配还有一个好处即如果需要增加或修改条件,那么不必对付一个巨大的函 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第14页 共20页 2007/1/29 20:03 数。只需增加或修改适合的定义即可。这消除了“四人帮”(GoF)书中的一大类设 计模式。条件越复杂,模式匹配就越有用。一旦习惯了它,你就会担心没有了模 式匹配的日子如何打发。 Closures 到此我们已经讨论了纯的函数式语言——实现了lambda演算又不包括与丘奇形式 系统矛盾的语言——环境里的特性,可是还有很多在lambda演算框架之外的函数 语言的有用特征。虽然一个公理系统的实现可以让我们象数学表达式那样思考程 序但它未必是实际可行的。许多语言选择去合并一些函数式的元素而没有严格的 坚持函数式的教条。很多象这样的语言(如Common Lisp)不要求变量是 final 的 ——可以即处对其修改。他们还不要求函数只依赖于其参数——允许函数访问外 部状态。但这些语言也的确包含着函数式的特征——如高阶函数,在非纯粹的函 数式语言里传递函数作为参数和限制在 lambda 演算系统中的作法有些不同,它 需要一种常被称为词法(lexical)closure 的有趣特性。下面我给出几个例子。记 住,这里变量不再是final的,函数可以引用其作用域外的变量: Function makePowerFn(int power) { int powerFn(int base) { return pow(base, power); } return powerFn; } Function square = makePowerFn(2); square(3); // returns 9 函数 make-power-fn 返回了一个函数,它有一个参数,并对这个参数进行一定阶 的幂运算。如果对 square(3) 求值会有什么结果?变量 power 不在 powerFn 的作 用域中,因为 makePowerFn 已经返回它的栈桢而不复存在。那么square如何工 作?一定是这个语言以某种方式将power的值保存了起来以便 square 使用。如果 我们再新建一个函数cube,用来计算参数的立方又会怎样?运行环境必须存储两 个power的拷贝,每个我们用 make-power-fn 生成的函数都用一个拷贝。保存这 些值的现象就被称为 closure。 closure 不只保存宿主函数的参数。例如,closure 可能会是这样: Function makeIncrementer() { int n = 0; int increment() { return ++n; } } Function inc1 = makeIncrementer(); Function inc2 = makeIncrementer(); inc1(); // returns 1; inc1(); // returns 2; inc1(); // returns 3; inc2(); // returns 1; inc2(); // returns 2; inc2(); // returns 3; 运行时已保存了n,所以递增器可以访问它,而且运行时为每个递增器都保存了一 个 n 的拷贝,即使这些拷贝本应在 makeIncrementer 返回时消失。这些代码被如何编译?closure 在底层是如何工作的?很幸运,我们 可以去幕后看看。 一点常识会很有帮助,首先会注意到的是局部变量的生命期不再由简单的作用域 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第15页 共20页 2007/1/29 20:03 限定而是不确定的。那么显然可以由此得出结论它们不再被保存在栈上——反之 必须被保存在堆上[8]。这样一来,closure 的实现就象我们前面讨论的函数一样 了,只是它还有一个指向周围变量的引用。 class some_function_t { SymbolTable parentScope; // … } 当一个 closure 引用了一个不在其作用域的变量时,它会在其祖先作用域中查找这 个引用。就是这样!Closure 将函数式和面向对象的世界紧密结合。当你创建了一 个包含了一些状态的类并把它传到别处时,考虑一下 closure。Closure 就是这样 在取出作用域中的变量的同时创建“成员变量”,所以你不必亲自去做这些! 下一步的计划? 关于函数式编程,本文作了浅显地讨论。有时候一次粗浅的射猎可能会进展为重 大的收获与我也受益匪浅。将来我还计划写写 category 理论,monad,函数式数 据结构,函数式语言中的类型(type)体系,函数式并发,函数式数据库等等还有很 多。如果我得以(在学习的过程中)写出了上述诸多主题中的一半,我的生命就 会完整了。还有,Google 是我们的朋友。 评论? 如果你有任何问题,意见或建议,请发到邮箱 coffee…@gmail.com。很高兴收到 你的反馈 =========================== [1] 我在2005年找工作时常常提出这个问题,当时我得到的是数量可观的一脸茫 然。想像一下,这些人至少每人会得到30万美元,如果他们理解了他们可以得到 的大部分工具。 [2] 这像是个悖论。物理学家和数学家被迫确认他们还不完全清楚是否宇宙万物遵 循着可以被数学描述的规则。 [3] 我一直厌恶提供了一堆枯燥的日期,人名和地点的纪年式历史课。对我而言, 历史是改变了这个世界的人的生活,是他们行为之后的个人动机,是他们得以影 响亿万生灵的体制。所以这个关于历史的小节注定无法完整,只讨论了于此关系 及其密切的人物与事件。 [4] 我在学习函数式编程的时候,很不喜欢术语 lambda,因为我没有真正理解它 的意义。在这个环境里,lambda 是一个函数,那个希腊字母只是方便书写的数学 记法。每当你听到 lambda 时,只要在脑中把它翻译成函数即可。 [5] 有趣的是 Java 的字符串是不可变更的,探讨这一离经叛道的设计的原因也非 常有趣,不过在这里会分散我们对原目标的注意力 [6] 大多数函数式编程语言的编译器能通过将递归尽可能转为迭代来进行优化,这 被称为尾递归。 [7] 相反未必成立,虽然有时可以证明两端代码等价,但这不是所有情况下都成 立。 [8] 这实际上不比存储在栈上慢,因为一旦引入了垃圾回收器,内存分配就成为了 一个O(1)的操作。 此文章在 科学, IT相关分类下 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第16页 共20页 2007/1/29 20:03 本站随机推荐阅读: 你的 Web 服务可以赢利吗? 外包业,中国VS印度 让Web2.0成功商业化 2006年 Web 2.0 将面临的10个问题 用户界面设计——用好的设计替代不好的 28 条评论 我也要说 1. realzhang | November 16th, 2006 at 12:09 am 很好的文章。谢谢翻译的辛苦劳动。 另外,校对刘凯清不尽职哦,好多错别字、笔误。 2. Debugger | November 16th, 2006 at 8:38 am 好文章阿。 3. 倪跃 | November 16th, 2006 at 10:00 am 很有用,多谢了 4. leon | November 16th, 2006 at 10:08 am kk, 前面两段的描述真tmd准确 5. 一直在爬网 : 函数�… | November 16th, 2006 at 10:18 am […] 虽然我对你的列表一无所知,但我的列表却是一大堆关于函数式编程的 文章。而这些基本上是最难阅读的了。它们用枯燥的学院派语言写成,即使 “在华尔街 行业浸淫十年的专家(veterans)”也不能理解函数式编程(也写 作FP)都在探讨些什么。如果你去问花旗集团(Citi Group)或德意志银行 (Deutsche Bank)的项目经理[1],为什么选择了 JMS 而不 Erlang,他们 可能回答不能在产业级的应用中使用学院派语言。问题是,一些最为复杂 的,有着最严格需求的系统却是用函数式编程元素写成。有些说法不能让人 信服。 […] 6. Zoom.Quiet | November 16th, 2006 at 10:53 am so Great! 收藏了! 世界上好工具好语言好文章太多,需要我们不断的收集翻译整理; 7. 任中方 | November 16th, 2006 at 11:42 am 讲得真不错,文章的开头惊人的相似。 8. 米粒之光华 » l… | November 16th, 2006 at 12:22 pm […] blog中文翻译 » 函数式编程另类指南 (tags: programming) 归类于: — 空心菜 @ 12:21 pm […] 9. lqw0205 | November 16th, 2006 at 2:41 pm 看来都冲着头两段来的… 呵呵,前两段写的真是好阿… 10. albertlee | November 16th, 2006 at 3:46 pm blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第17页 共20页 2007/1/29 20:03 Continuation 的讨论比较清晰 以 Haskell Curry 的名字命名的 Haskell语言,是纯FP, 使用可怕的 Monad 现在我终于知道 monad 为什么存在了……为了保证执行顺序(我的理解对 么?) 11. charlee | November 16th, 2006 at 4:46 pm 这篇文章真得很不错,第一次知道原来有函数式编程这种概念。谢谢了 12. ’s blog » lin… | November 16th, 2006 at 6:26 pm […] blog中文翻译 » 函数式编程另类指南 (tags: programming fp) […] 13. lihaitao | November 16th, 2006 at 7:16 pm 谢谢大家的反馈。很抱歉译文中留下了很多错误,因为文章很长,译出后和 凯清进行了连续多次的审读,其实是有些麻木了,然后就迫不及待地发表出 来了。也许再放置一两个星期后重新校对会好些。 第一次翻译,虚心请教这里各位译者的翻译经验和流程。 14. Anonymous | November 17th, 2006 at 9:07 am Java看着头晕,也许可以补充一下,把对应的Haskell语句也列出来? 15. flip | November 17th, 2006 at 9:11 am Java看着挺头晕的,也许可以补充一下,把Haskell简洁的语句也列出来? 16. ox | November 17th, 2006 at 9:42 am 非常好的文章 我正在学习haskell 前不久刚刚把思维方式转变过来 逐渐发现了FP的力量 17. InterMa | November 17th, 2006 at 10:34 am Wonderful! 18. dieken | November 17th, 2006 at 10:51 am 强烈赞叹,非常的期待“下一步计划”。 19. libin » 博客文�… | November 17th, 2006 at 6:13 pm […] 虽然我对你的列表一无所知,但我的列表却是一大堆关于函数式编程的 文章。而这些基本上是最难阅读的了。它们用枯燥的学院派语言写成,即 使”在华尔街行业浸淫十年的专家(veterans)”也不能理解函数式编程(也 写作FP)都在探讨些什么。如果你去问花旗集团(Citi Group)或德意志银 行(Deutsche Bank)的项目经理[ 1],为什么选择了 JMS 而不 Erlang,他 们可能回答不能在产业级的应用中使用学院派语言。问题是,一些最为复杂 的,有着最严格需求的系统却是用函数式编程元素写成。有些说法不能让人 信服。 […] 20. wzboy | November 17th, 2006 at 7:38 pm 太长了,存入列表^^ 21. 11ff | November 21st, 2006 at 6:41 pm blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第18页 共20页 2007/1/29 20:03 太长了,存入列表^^ 22. S Jin | November 23rd, 2006 at 4:10 am 翻译的非常棒, 但有几处值得和你商榷, 这样读者就不会看晕了。不过这次先 邀请你翻译 MPlayer 的手册页(man page)文档。 是开源的跨平台多媒体播放软件, 能播放几乎所有的格式。这么好的软件, 我 不忍心 MPlayer 没有中文版。手册页我已翻了大半, 还剩下两节我可能是实 在来不及。愿意的话先看 http://www.mplayerhq.hu/DOCS/tech/translations.txt , 同时还能玩玩 SVN 版本控制, 挺有意思。 23. Running Sandwitch »… | November 26th, 2006 at 11:57 am […] blog中文翻译 » 函数式编程另类指南 (tags: functional programming lambda church) […] 24. tsbob | November 26th, 2006 at 8:06 pm 哈哈,前两段真写实!! 文章很不错,字里行间透露处作者是软件开发领域融会贯通的高手 25. lihaitao | November 30th, 2006 at 11:00 pm 对于公共领域(Public Domain)和创作共享(Creative Commons)的文字,翻 译质量的保障和提升有赖于大家的参与,这篇文章已经加入洛基开放文化实 验室, 永久链接 http://rl.rockiestech.com/node/172, 如果发现了错误和不 妥,你可以指出或直接更正 26. Eri(c)Liu » 【收�… | December 12th, 2006 at 1:07 pm […] 函数式编程另类指南 http://chn.blogbeta.com/232.html 归类于: Uncategorized — 瞎折腾 @ 1:06 pm […] 27. 田松 | January 9th, 2007 at 1:46 am 好,很好。好的无法用语言表达。 28. yejianhui | January 18th, 2007 at 10:26 am 这篇文章是我近年来看的介绍函数式语言说得最好的一篇! 多谢lihaitao的翻译 您的评论,对我们很重要 名字 Email *为您保密 网站 评论 blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第19页 共20页 2007/1/29 20:03 发表 Trackback这篇文章 | 通过RSS Feed订阅评论 日历 January 2007 MTWTFSS « Dec 123456 7 8 9 1011121314 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 最新文章 从技术角度解释为何圣 诞老人能在一夜之间将 礼物送达世界各地 女人、服装、潮流、嗯 超越用户创建的模式: Web 2.0 和语义网络 Page View 将死? 经济101管理方法 Web 2.0 的视觉设计 改变未来IT业发展的趋 势 在线协作,你选择哪一 种方式? blog中文翻译 » 函数式编程另类指南 http://chn.blogbeta.com/232.html 第20页 共20页 2007/1/29 20:03 未来的web2.0社交体验 不要反对互联网 i.blogbeta city8升级多项功能,逐 步实现在网上溜达北京 QQVideo改版,越来越 像2.0产品 G宝盘开始测试BT下载 支持 腾讯网推“儿童频道”,让 人喜忧各半 ShopEx发布客户端程序 “ShopEx助理” Linkist新版上线,主推基 于SNS与Digg的信息分 享 G宝盘提供Flash上传与 文档在线阅读功能 Yupoo新年改版,强化 社区功能 巴巴变“印记·祝福”活动 圆满结束 贝壳第二次公测,试水 数码体验社区 O..F..Blog 大明王朝全是鬼,伊斯 坦布尔刚好相反 【纵横周刊·伊朗】年度 预算将如何通过? 精心设计,用力推销, 儿童产品成功的两个因 素 极速逼近3000点,中国股 市泡沫声刺耳 润物无声的杨亚洲影视 作品(下) 润物无声的杨亚洲影视 作品(上) 走,大家一起去秀 “宫S”来了 写给孩子的《洋葱头历 险记》 星巴克进故宫,才多大的 事儿? 本站采用创作共享版权协议,要求署名、非商业和保持一致。本站欢迎任何非商业应用的转载,但须 注明出自“blog中文翻译”,保留原始链接,此外还必须标注英文原文标题和链接。提醒:如果转载本 站文章,你的站点也必须遵循同样的创作共用协议 - ZhanBin - BlogBeta - O..F..Blog - Wishine - Powered by WordPress . Login
还剩19页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

lakedai

贡献于2014-07-23

下载需要 5 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf