函数式编程语言是那些方便于使用函数式编程范式的语言。简单来讲,若是具有函数式编程所需的特征, 它就能够被称为函数式语言。在多数状况下,编程的风格实际上决定了一个程序是不是函数式的。 javascript
函数式编程没法用C语言来实现。函数式编程也没法用Java来实现(不包括那些经过大量变通手段实现的近似函数式编程)。 这些语言不包含支持函数式编程的结构。他们是纯面向对象的、严格非函数式的语言。 html
同时,纯函数语言也没法使用面向对象编程,好比Scheme、Haskell以及Lisp。java
然而有些语言两种模式都支持。Python是个著名的例子,不过还有别的:Ruby,Julia,以及咱们最感兴趣的Javascript。 这些语言是如何支持这两种差异如此之大的设计模式呢?它们包含两种编程范式所须要的特征。 然而对于Javascript来讲,函数式的特征彷佛是被隐藏了。 程序员
但实际上,函数式语言所须要的比上述要多一些。到底函数式语言有什么特征呢?编程
特色 | 命令式 | 函数式 |
---|---|---|
编程风格 | 一步一步地执行,而且要管理状态的变化 | 描述问题和和所需的数据变化以解决问题 |
状态变化 | 很重要 | 不存在 |
执行顺序 | 很重要 | 不过重要 |
主要的控制流 | 循环、条件、函数调用 | 函数调用和递归 |
主要的操做单元 | 结构体和类对象 | 函数做为一等公民的对象和数据集 |
函数式语言的语法必需要顾及到特定的设计模式,好比类型推断系统和匿名函数。大致上,这个语言必须实现lambda演算。 而且解释器的求值策略必须是非严格、按需调用的(也叫作延迟执行),它容许不变数据结构和非严格、惰性求值。 设计模式
译注:这一段用了一些函数式编程的专业词汇。lambda演算是一套函数推演的形式化系统(听起来很晕), 它的先决条件是内部函数和匿名函数。非严格求值和惰性求值差很少一个意思,就是并不是严格地按照运算规则把全部元素先计算一遍, 而是根据最终的需求只计算有用的那一部分,好比咱们要取有一百个元素的数组的前三项, 那惰性求值实际只会计算出一个具备三个元素是数组,而不会先去计算那个一百个元素的数组。 数组
当你最终掌握了函数式编程它将给你巨大的启迪。这样的经验会让你后面的程序员生涯更上一个台阶, 不管你是否真的会成为一个全职的函数式程序员。 数据结构
不过咱们如今不是在讨论如何去学习冥想;咱们正在探讨如何去学习一个很是有用的工具,它将会让你成为一个更好的程序员。闭包
总的来讲,什么是使用函数式编程真正实际的优势呢?编程语言
函数式编程更简洁、更简单、更小。它简化了调试、测试和维护。
例如,咱们须要这样一个函数,它能将二维数组转化为一维数组。若是只用命令式的技术,咱们会写成这样:
function merge2dArrayIntoOne(arrays) { var count = arrays.length; var merged = new Array(count); var c = 0; for (var i = 0; i < count; ++i) { for (var j = 0, jlen = arrays[i].length; j < jlen; ++j) { merged[c++] = arrays[i][j]; } } return merged }
如今使用函数式技术,能够写成这样:
merge2dArrayIntoOne2 = (arrays) -> arrays.reduce (memo, item) -> memo.concat item
var merge2dArrayIntoOne2 = function(arrays) { return arrays.reduce( function(p,n){ return p.concat(n); }); };
这两个函数具备一样的输入并返回相同的输出,可是函数式的例子更简洁。
函数式编程强制把大型问题拆分红解决一样问题的更小的情形,这就意味着代码会更加模块化。 模块化的程序具备更清晰的描述,更易调试,维护起来也更简单。测试也会变得更加容易, 这是因为每个模块的代码均可以单独检测正确性。
因为其模块化的特性,函数式编程会有许多通用的辅助函数。你将会发现这里面的许多函数能够在大量不一样的应用里重用。
在后面的章节里,许多最通用的函数将会被覆盖到。然而,做为一个函数式程序员,你将会不可避免地编写本身的函数库, 这些函数会被一次又一次地使用。例如一个用于在行间查找配置文件的函数,若是设计好了也能够用于查找Hash表。
耦合是程序里模块间的大量依赖。因为函数式编程遵循编写一等公民的、高阶的纯函数, 这使得它们对全局变量没有反作用而彼此彻底独立,耦合极大程度上的减少了。 固然,函数会不可避免地相互依赖,可是改变一个函数不会影响其余的,只要输入和输出的一对一映射保持正确。
最后一点更理论一些。因为根植于lambda演算,函数式编程能够在数学上证实正确性。 这对于一些研究者来讲是一个巨大的优势,他们须要用程序来证实增加率、时间复杂度以及数学正确性。
咱们来看看斐波那契数列。尽管它不多用于概念性证实之外的问题,可是用它来解释这个概念很是好。 对一个斐波那契数列求值标准的办法是创建一个递归函数,像这样:
fibonnaci(n) = fibonnaci(n-2) + fibonnaci(n–1)
还须要加上一个通常情形:
return 1 when n < 2
这使得递归能够终止,而且让递归调用栈里的每一步从这里开始累加。
下面列出详细步骤
var fibonacci = function(n) { if (n < 2) { return 1; }else { return fibonacci(n - 2) + fibonacci(n - 1); } } console.log( fibonacci(8) ); // Output: 34
然而,在一个懒执行函数库的辅助下,能够生成一个无穷大的序列,它是经过数学方程来定义整个序列的成员的。 只有那些咱们最终须要的成员最后才会被计算出来。
var fibonacci2 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci2.length()); // Output: undefined console.log(fibonacci2.take(12).toArray()); // Output: [1, 1, 2, 3, 5,8, 13, 21, 34, 55, 89, 144] var fibonacci3 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci3.take(9).reverse().first(1).toArray()); //Output: [34]
第二个例子明显更有数学的味道。它依赖Lazy.js函数库。还有一些其它这样的库,好比Sloth.js、wu.js, 这些将在第三章里面讲到。
我插几句:后面这个懒执行的例子放这彷佛仅仅是来秀一下函数式编程在数学正确性上的表现。 更让人奇怪的是做者还要把具备相同内部函数的懒加载写两遍,彻底没意义啊…… 我以为各位看官知道这是个懒执就好了,没必要深究。
函数式和非函数式编程能混合在一块儿吗?尽管这是第七章的主题,可是在咱们进一步学习以前, 仍是要弄明白一些东西。
这本书并没要想要教你如何严格地用纯函数编程来实现整个应用。这样的应用在学术界以外不太适合。 相反,这本书是要教你如何在必要的命令式代码之上使用纯函数的设计策略。
例如,你须要在一段文本中找出头四个只含有字母的单词,稚嫩一些的写法会是这样:
var words = [], count = 0; text = myString.split(' '); for (i=0; count < 4, i < text.length; i++) { if (!text[i].match(/[0-9]/)) { words = words.concat(text[i]); count++; } } console.log(words);
函数式编程会写成这样:
var words = []; var words = myString.split(' ').filter(function(x){ return (! x.match(/[1-9]+/)); }).slice(0,4); console.log(words);
若是有一个函数式编程的工具库,代码能够进一步被简化:
var words = toSequence(myString).match(/[a-zA-Z]+/).first(4);
判断一个函数是否能被写成更加函数式的方式是寻找循环和临时变量,好比前面例子里面的“words”和”count”变量。 咱们一般能够用高阶函数来替换循环和临时变量,本章后面的部分将对其继续探索。
如今还有最后一个问题咱们须要问问本身,Javascript是函数式语言仍是非函数式语言?
Javascript能够说是世界上最流行却最没有被理解的函数式编程语言。Javascript是一个披着C外衣的函数式编程语言。 它的语法无疑和C比较像,这意味着它使用C语言的块式语法和中缀语序。而且它是现存语言中名字起得最差劲的。 你不用去想象就能够看出来有多少人会因Javascript和Java的关系而迷惑,就好像它的名字暗示了它会是什么样的东西! 但实际上它和Java的共同点很是少。不过还真有一些要把Javascript强制弄成面向对象语言的主意, 好比Dojo、ease.js这些库曾作了大量工做试图抽象Javascript以使其适合面向对象编程。 Javascript来自于90年代那个满世界都嚷嚷着面向对象的时代,咱们被告知Javascript是一个面向对象语言是由于咱们但愿它是这样, 但实际上它不是。
它的真实身份能够追溯到它的原型:Scheme和Lisp,两个经典的函数式编程语言。Javascript一直都是一个函数式编程语言。 它的函数是头等公民,而且能够嵌套,它具备闭包和复合函数,它容许珂理化和monad。全部这些都是函数式编程的关键。 这里另外还有一些Javascript是函数式语言的缘由:
也就是说,Javascript的确不是一个纯函数式语言。它缺少惰性求值和内建的不可变数据。 这是因为大多数解释器是按名调用,而不是按需调用。Javascript因为其尾调用的处理方式也不太善于处理递归。 不过全部的这些问题均可以经过一些小的注意事项来缓和。须要无穷序列和惰性求值的非严格求值能够经过一个叫Lazy.js的库来实现。 不可变量只须要简单的经过编程技巧就能够实现,不过它不是经过依赖语言层面来限制而是须要程序员自律。 尾递归消除能够经过一个叫Trampolining的方法实现。这些问题将在第六章讲解。
关于Javascript是函数式语言仍是面向对象语言仍是二者皆是仍是二者皆非的争论一直都不少,并且这些争论还要继续下去。
最后,函数式编程是经过巧妙的变化、组合、使用函数而实现编写简洁代码的方式。并且Javascript为实现这些提供了很好的途径。 若是你真要挖掘出Javascript所有的潜能,你必须学会如何将它做为一个函数式语言来使用。