五、函数式

这个可不是JavaScript的发明,它的发明人已经死了,而他的这个发明还在困扰着我们……如同爱迪生的灯
泡还在照耀着我们。

其实函数式语言很简单,它就是一种与命令式语言同样“完备”的语言实现方案。由于它的基础思想与命令
式——如果你不想用这个难于理解的名词,那就把它换成C,或者Delphi好了——语言完全不同,所以大多数
情况下,它也与这些传统的、通用的、商业化的语言格格不入。

而事实上,你天天都在用它。

下面这行代码,就充满了函数式语言的思想:

  1. a + b

是吗?真的,如果你把那个“+”号看成一个函数,就完全一样了。事实上,所谓函数(function),就是一个
执行过程、一段运算、一个功能入口……也就是说,代码中的某个东西,要么它是数据,要么它是运算。而如
果它是运算,你就可以把它看成“函数”。上面这行代码——表达式中,a和b显然是数据,而+号则是对之进
行操作的运算,所以它自然可以看成一个“功能、过程或运算”。所以……操作两个数求和的“函数”可以写
成这样:

  1. func_add(a, b)

或者这样:

  1. (+ a b)

所有这些,只是文字上的标记法不同而已。就好象我说这个符号是“jia”,而你非得说它是“暗得”,另一个
人却非要读作“a-d-d”。有什么不同吗?没有。

所有程序员天天都在写语句,写表达式,与运算逻辑。大家都知道这些东西都可以写在一个……嗯……无比巨
大的函数里,或者分在无数个小的、看起来很漂亮的函数里。当所有这些函数一个又一个连续起来运算时,它
就成了“函数式语言”。所以称为“函数式语言的祖老爷爷”的LISP就是这样“把函数运算连起来”的语言:

  1. (defun subst (x y z)
  2. (cond ((atom z)
  3. (cond ((eq z y) x)
  4. ('t z)))
  5. ('t (cons (subst x y (car z))
  6. (subst x y (cdr z))))))

有人认为它是丑陋的意大利面条,也有人认为它是最简洁的语言。Oh,随你,了解了就好了,管它象什么尼。

由于函数式语言只有连续执行的函数——所以它没有了“语句”而又必须实现逻辑上的完整性。简单地说,他
需要在连续执行的过程中实现“顺序、分支与循环”三个基本逻辑。函数式的解决之道,是用三元条件运算来
代替if/then/else,也就下面的代码:

  1. if a then b else c
  2. // 相当于
  3. a ? b : c

另外,就是用递归(函数调函数)来实现循环。考虑到递归函数调用中会导致栈溢出(Stack overflow at ...),
所以函数式语言就提出了“尾递归”,也就是在书写代码是“确保”仅只在函数内的最后一个运算中递归调用。
这样一来,这个递归调用就不需要保留栈了——因为再没有后续运算了,因此就能被优化成一行不需要返回的
jmp汇编指令。

世界真美好,所谓函数式不过是一堆运算入口,以及jmp、jmp、jmp。但,但是,难道不是吗——这块CPU?

六、更高级的函数式(1)

其实世界并不美好。

如果一块CPU死躺在那里,它也就只有顺序呀、分支啦、循环之类的指令在里头。但是当它运行起来,就得有一
个时钟在滴嗒…滴嗒…如果是多核的,还会同时有好几个这样的东东在滴嗒着。

这就是问题,世界原本不是单一时序,也不是守序的。所以会有并发,会有中断,也会有异常。函数式语言应
该自己应该像意大利面条一样无限延展开去,那么是不是有多个滴嗒时就该有多根意大利面条呢?但,这种情
况下还叫意大利面条吗?

函数式里的解决方案叫延续(Continuation)和结点。延续是停止当前转到另一处、然后再返回来;而结点则确
保一个位置上的代码是独立完备的,与另一个结点无关。在函数式语言中,多个结点就象平行宇宙,大多数情
况下它们是互相透明的,如果它们要发生联系,得需要极其巨大的能量,以及……一个虫洞。

多个结点的问题我们不需要深究,多个空间下的访问带来的时空问题,是天体学家以及时空多维理论家们研究
的问题,我们只需要知道:如果你希望多个结点之间存在交叉的访问关系,那么世界/宇宙会因此毁灭。这样类
似的说明写在ErLang这类天生支持多个宇宙的语言的白皮书、参考手册以及宇航员日常指南之中。如果你要穷
究其根源,并且认为自己已经能明确了解300个以上的高等数字、物理学和天体学术语,地么下面有一份面向程
序员的入口指引,不妨从这里开始:
http://www.blogjava.net/canonical/archive/2007/12/05/165664.html

延续是解决状态问题的方法之一。简单说来,有时间就有状态:有过去,现在和将来。对于过去,我们有历史,
也就会因为过去发生了什么而决定现在发生什么,例如因为昨天喝高了小酒,所以今天只能吃稀粥;而现在也
就是明天的昨天,所以要为了明天能做什么而记录下今天的状态,例如今天已经吃了稀粥;至于明天,当然,
将要发生的事情很多,我们得一件一件地做好准备。

Oh,重要的就是这个“做好准备”了。这在计算机系统里叫:事件。或者叫计划,或者叫突发。知道为什么我
们的PC机可以运行吗?En...是因为有一个指令流水线在按照一个足够微小的时间片去执行指令。对于运算系统
来说,这个定时发生的指令访问行为,就是中断。所以有一天一个朋友问我:如果执行逻辑只有顺序、分支与
循环,那么流程系统中的触发器算什么?我想了很久,无解。而现在来回答这个问题,就得把“时间”这个维
度加上,所谓突发、并发以及类似的东西,是时序概念下的逻辑。

好了,当我们“做好准备”,为了将来要触发某个东西做准备的时候——简单的说,就当是个时钟处理程序好
了(在windows中它叫OnTimer,在浏览器中它叫setTimeout/setInterval),为了这个时候我们能够在函数式
语言中做好一顿意大利面条,我们说明:“我们已经做好了准备”,以及“我们做好了怎样的准备”。而按照
函数式的基本原则,一个函数是与状态无关的,它与“将来”这样一个时序无关。这,变成了一个矛盾。

事实上开始我们已经与这个矛盾正面冲突了一次,这就是“循环”。因为循环需要一个状态量来指示循环进度,
这个“进度”就是时序相关的。而函数式使用了“递归”来解决它,也就是通过递归函数的参数来传递这个进
度。在“递归参数”——这个界面的两边,函数式都是时序无关的。由此带来的问题就是栈溢出,解决方法则
是尾递归;又由此带来的问题就是编程复杂性,以及能否证明尾递归能替代所有递归;解决方案是……温伯格
说得没错,所有解决问题的方法都会带来新的问题。Oh...又是温伯格。

现在,我们明确的需要“更多的状态”了,因为我们已经将系统运行一个或是多个的时序里——也就CPU。即使
我们有结点,而且保证“没有虫洞”,那么我们也需要解决一个CPU中的过去、现在与将来的问题。

函数式的仙家们说了两个字:持续。简单啊,就是把为过去、现在的状态,和为将来的准备作为函数的参数传过
去。看起来,“现在”立即就要爆炸了,因为既要包括过去的、现在的状态以及变化,还要包括将来的运算。于
是新的解决方案是:若要现在不爆炸,"持续"的界面上不要发生运算就好了。

运算由“将来”根据“过去”的数据做决策,这就是持续。支持它的函数式特性,就是在惰性求值。简单的说,

  1. function f(x, c) {
  2. ...
  3. c(x);
  4. }
  5. f(data, f);

由于在传送界面f()上,c本身是不求值的,所以爆炸会发生在/不会发生都是在将来c(x)进行运算的时候。如果
宇宙的熵有极限,那么这个极限也是在末可知的将来。而且,在末可知的将来,也有可能回扭曲回现在。这就是
持续的两个原则:
--------------------
一个成功持续之后,是一个新的持续(或过程)
一个失败持续将回到上一个选择点
--------------------
正是因为有持续的状态,且这个状态及持续本身都是通过函数参数传递的,所以,“回到选择点”只是将来自身
的一个决择,与现在的状态是无关的。

七、更高级的函数式(2)

无论如何,种种函数式的复杂性,确实是在编程范型中保持一种自我的纯粹性而存在的。例如生成器(产生器)
这个问题,由于一批数据的产生之间是有关系的(例如增量),而产生过程是时序相关的(不总是在一次调用中
得到全部的数据),因此函数式语言定义了一个“生成器”这样的函数概念。我们可以在生成器中定义一个yield,
这个返回就是“将来”回到这个“现在”的一个虫洞。通过这个虫洞,我们可以拿到一个时序相关的数据。下面
就是这样的一个例子(mozilla上的斐波那契数列示例):

  1. <!-- for firefox 3 -->
  2. <mce:script type="application/javascript;version=1.7"><!--
  3. function fib() {
  4. var i = 0, j = 1;
  5. while (true) {
  6. yield i;        //<-虫洞在这里
  7. var t = i;
  8. i = j;
  9. j += t;
  10. }
  11. }
  12. var g = fib();
  13. a = g.next();
  14. b = g.next();
  15. c = g.next();
  16. // ...
  17. // .. 三天以后(或一个循环以后),某时空旅行家想回到fib里去看看
  18. z = g.next();
  19. // 呵,原来是值“2”
  20. alert(z);
  21. // --></mce:script>

八、结语

好象,好象这篇《无废话》有很多废话……哈哈,玩笑啦,真的全无废话,还是活人能看的木?

真的那么想读无废话,你应该去读大学教材了。

无废话JavaScript(下)的更多相关文章

  1. 无废话JavaScript(上)

    <程序员>2008.09期有一篇名为<无废话ErLang>的文章,这让我想到了许多的诸如“无废话C”.“无废话书评”这类的文章,也想到了JavaScript可没有一篇“无废话” ...

  2. 无废话ExtJs 入门教程十七[列表:GridPanel]

    无废话ExtJs 入门教程十七[列表:GridPanel] extjs技术交流,欢迎加群(201926085) 在Extjs中,GridPanel用于数据显示,即我们平时说的列表页.在本节中,我们先对 ...

  3. 无废话ExtJs 入门教程十五[员工信息表Demo:AddUser]

    无废话ExtJs 入门教程十五[员工信息表Demo:AddUser] extjs技术交流,欢迎加群(201926085) 前面我们共介绍过10种表单组件,这些组件是我们在开发过程中最经常用到的,所以一 ...

  4. 无废话ExtJs 入门教程十一[下拉列表:Combobox]

    无废话ExtJs 入门教程十一[下拉列表:Combobox] extjs技术交流,欢迎加群(201926085) 继上一节内容,我们在表单里加了个一个下拉列表: 1.代码如下: 1 <!DOCT ...

  5. 无废话ExtJs 入门教程二十一[继承:Extend]

    无废话ExtJs 入门教程二十一[继承:Extend] extjs技术交流,欢迎加群(201926085) 在开发中,我们在使用视图组件时,经常要设置宽度,高度,标题等属性.而这些属性可以通过“继承” ...

  6. 无废话ExtJs 入门教程二十[数据交互:AJAX]

    无废话ExtJs 入门教程二十[数据交互:AJAX] extjs技术交流,欢迎加群(521711109) 1.代码如下: 1 <!DOCTYPE html PUBLIC "-//W3C ...

  7. 无废话WCF入门教程六[一个简单的Demo]

    一.前言 前面的几个章节介绍了很多理论基础,如:什么是WCF.WCF中的A.B.C.WCF的传输模式.本文从零开始和大家一起写一个小的WCF应用程序Demo. 大多框架的学习都是从增.删.改.查开始来 ...

  8. 无废话ExtJs 入门教程十六[页面布局:Layout]

    无废话ExtJs 入门教程十六[页面布局:Layout] extjs技术交流,欢迎加群(201926085) 首先解释什么是布局: 来自百度词典的官方解释:◎ 布局 bùjú: [distributi ...

  9. 无废话ExtJs 入门教程十四[文本编辑器:Editor]

    无废话ExtJs 入门教程十四[文本编辑器:Editor] extjs技术交流,欢迎加群(201926085) ExtJs自带的编辑器没有图片上传的功能,大部分时候能够满足我们的需要. 但有时候这个功 ...

随机推荐

  1. 关于虚拟机安装mac os 教程详解

    环境搭建 VMware下载 百度云盘下载:链接:http://pan.baidu.com/s/1pK8RcLl 密码:5jc5 Unlocker208 百度云盘下载:链接:http://pan.bai ...

  2. 给新建的kvm虚拟机创建网络接口

    (一)首先必须创建网卡连接桥接口的启动脚本和停止脚本,其中脚本中的 $1:表示为虚拟机的网卡的右边接口,这两个脚本就是讲虚拟机的网卡的右边接口接在网桥上,实现桥接模型     # 1:/etc/qem ...

  3. MySQL 查询缓存机制(MySQL数据库调优)

    查询缓存机制:缓存的是查询语句的整个查询结果,是一个完整的select语句的缓存结果 哪些查询可能不会被缓存 :查询中包含UDF.存储函数.用户自定义变量.临时表.mysql库中系统表.或者包含列级别 ...

  4. 【BioCode】根据seq与位点信息截取窗口

    代码说明 sequence24371.txt 以上为所有氨基酸的编号,序列,与位点标记.根据标记为“1”的位点,截取窗口:如下(实验结果): 图示为一个窗口为12的蛋白质片段 2N+1=25: 实现代 ...

  5. 20个PHP程序性能优化的方法

    1.数据库连接当使用完毕时应当关闭,不要使用长连接. 2.用@屏蔽错误消息的做法非常低效,极其低效. 3.使用选择分支语句(switch case)好于使用多个if,else if. 4.$row[' ...

  6. 利用SSH secure Shell实现windows与linux之间传输文件

    在windows下安装SSH secure Shell.默认安装后有两个快捷方式. linux下需要安装openssh-server utuntu默认安装了opens是-client,所以不需要安装, ...

  7. 第187天:js基础---常见的Bom对象

    BOM(Browser Object Mode)浏览器对象模型,是Javascript的重要组成部分.它提供了一系列对象用于与浏览器窗口进行交互,这些对象通常统称为BOM. 一张图了解一下先 1.wi ...

  8. [BZOJ3172]单词

    3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MB Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会 ...

  9. 【BZOJ4709】柠檬(动态规划,单调栈)

    [BZOJ4709]柠檬(动态规划,单调栈) 题面 BZOJ 题解 从左取和从右取没有区别,本质上就是要分段. 设\(f[i]\)表示前\(i\)个位置的最大值. 那么相当于我们枚举一个前面的位置\( ...

  10. LCA的倍增算法

    LCA,即树上两点之间的公共祖先,求这样一个公共祖先有很多种方法: 暴力向上:O(n) 每次将深度大的点往上移动,直至二者相遇 树剖:O(logn) 在O(2n)预处理重链之后,每次就将深度大的沿重链 ...