函数式编程 ( Functional Programming ) 是一种以函数为基础的编程方式和代码组织方式,能够带来更好的代码调试及项目维护的优势。本篇主要结合笔者在实际项目开发中的一些应用,简要谈谈函数式编程。
函数
在函数式编程中,任何代码可以都是函数,且要求具有返回值,如下示例
// 非函数式 var title = "Functional Programming"; var saying = "This is not"; console.log(saying + title); // => This is not Functional Programming
// 函数式
var say = title => "This is " + title;
var text = say("Functional Programming"); // => This is Functional Programming
纯函数
纯函数在这里指函数内外间是“无”关联的。主要有下面两点
没有副作用(side effect)
不会涉及到外部变量的使用或修改引用透明
函数内只会依赖传入参数,在任何时候对函数输入相同的参数时,总能输出相同的结果
// 非纯函数(函数内依赖函数外的变量值) var title = "Functional Programming"; var say = ()=> "This is not" + title; // <= 依赖了全局变量 title
// 纯函数
var say = (title)=>"This is " + title; // <= 依赖了以参数 title 传入
say("Functional Programming");
不可变数据(immutable)
这里主要是指变量值的不可变。当需要基于原变量值改变时,可通过产生新的变量来确保原变量的不变性,如下
// 可变数据 var arr = ["Functional", "Programming"]; arr[0] = "Other"; // <= 修改了arr[0]的值 console.log(arr) // => ["Other", "Programming"] // 变量arr值已经被修改
// 不可变数据
var arr = ["Functional", "Programming"];// 得到新的变量,不修改了原来的值
var newArr = arr.map(item => {
if(item === "Functional"){
return "Other";
} else {
return item;
}
})
console.log(arr); // => ["Functional", "Programming"] 变量arr值不变
console.log(newArr); // => ["Other", "Programming"] 产生新的变量newArr
之所以使用这种不变值,除了更好的函数式编程外,还能够维持线程安全可靠,落地在业务中,实际上也能让代码更加清晰。
设想,如果你定义了一个变量A,A在其他地方被其他人修改了,这样是不方便定位A的当前值的。关于定义多个变量引发的内存等问题,可以通过重用结构或部分引用的方式来减轻,可参考 immutable.js
使用 map, reduce 等数据处理函数
强大的 JavaScript 有着越来越多的高能处理数据函数,其中包含了 map、 reduce、 filter 等。
map 能够对原数组中的值进行逐个处理并产生新的数组,一个简单例子
// map var data = [1, 2, 3]; var squares = data.map( (item, index, array) => item * item ); console.log(squares); // => [1, 4, 9] console.log(data);// => [1, 2, 3] data 还是那个 data
reduce 能够对原数组中的各个值进行结合处理,来产生新的值,如下面例子中,previous 代表上一个值,current 代表当前值,reduce 函数可以传入第二个参数作为 previous 初始值,不传时则 previous 初始值为数组中第一个值。
// reduce var sum = [1, 2, 3].reduce( (previous, current, index, array) => previous + current ); console.log(sum); // => 6
函数柯里化 Currying
柯里化 是将多参函数转换成一系列的单参函数。结合下面例子来说明下
// 一个多参函数 var add = (a, b) => a + b; add(1, 2); // => 3
将上面的多参函数进行柯里化,如下
// 柯里化函数 var add = a => b => a + b;
上面柯里化后的函数调用方式也有所转变,第一次传入一个参数返回了一个函数,再传入参数则完成整体的调用,这也是利用的闭包的特性
var add1 = add(1); add1(2); // => 3
柯里化后的函数,也可以应用在生产 “ 函数 ” 上,如下示例
var say = title => type => title + " is " + type;
var sayFP = say("Functional Programming");
var sayOther = say("Other Programming");
sayFP("good"); // => Functional Programming is good
sayOther("good"); // => Other Programming is good
组合函数 compose
顾名思义,组合函数是将多个函数进行组合成一个函数。举个例子
var compose = (fn1, fn2) => (arg) => fn1(fn2(arg));
var a = arg => arg + 'a';
var b = arg => arg + 'b';
var c = compose(a, b); // 将a,b函数进行组合
c('c'); // => cba
上面示例中,当调用组合函数 c 时,传入的参数会经过 b 函数,接着将 b 函数的返回值作为 a 函数的参数值,从而输出最终结果。
组合函数 c 就像管道一样,将水流( 返回值 )流经各个函数中进行处理。
当想要组合很多函数成一条很长很长的“管道”时,那么显然上面的 compose 函数已经不够用了。下面看看 redux 是怎么做这个 compose 工具函数的。
// 源自: redux/src/compose.js
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
} else {
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
}
代码很简洁,主要利用了递归方式和数组的 reduceRight 方法来处理,reduceRight 跟上边提到的 reduce 方法功能是一样的,不同的是 reduceRight 是从数组的末尾向前逐个处理。就这样,想拼多长的就多长。
以上,便是笔者在项目实践中应用较多的函数式编程内容,如有不妥,请斧正。
附: 一些可供学习函数式编程的内容
Immutable.js (https://facebook.github.io/immutable-js/)
Underscore (http://underscorejs.org/)
Lodash (https://lodash.com/)
Ramda (http://ramdajs.com/)
Monads (http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html)
转自:joeyguo个人博客
$arr = ['Client'=>'jQuery','Server'=>'PHP'];
array_walk($arr, function($v, $k) {
echo "键:$k 值:$v\n";
});
比如去除数组$arr元素的前后空白:
array_walk($arr, function(&$v) { $v = trim($v); });
foreach($arr as &$v) { $v = trim($v); }
array_filter: 用回调函数过滤数组中的单元,返回过滤后的数组
var_export(
array_filter([1, 2, 3], function($v) {
return $v > 1;
})
);
和
foreach([1, 2, 3] as $k => $v) {
if($v > 1) {
$tmp[$k] = $v;
}
}
var_export($tmp);
都输出:
array (
1 => 2,
2 => 3,
)
array_map: 将回调函数作用到给定数组的单元上
var_export(
array_map(function ($v) {
return $v * $v;
}, [1, 2, 3])
);
和
foreach([1, 2, 3] as $v) {
$tmp[] = $v * $v;
}
var_export($tmp);
都输出:
array (
0 => 1,
1 => 4,
2 => 9,
)
array_reduce: 用回调函数迭代地将数组简化(reduce)为单一的值
//输出16,即10+1+2+3,其中10作为初始值.
echo array_reduce([1, 2, 3], function($result, $item) {
$result = $result + $item;
return $result;
}, 10);
用foreach表达:
$result = 10;
foreach([1, 2, 3] as $v) {
$result = $result + $v;
}
echo $result;
引用来自“crab2313”的评论
楼上几位的评论就跟“只学c的说oop没有任何鸟用就是层语法”这个说法如出一辙引用来自“kchr”的评论
不是。只是说函数式编程主要是设计给逻辑学家,研究计算的完备性等各种数学概念用的,各种特性有利于数学推理,不利于工程中使用。
可以学来玩玩,但不要被那些扯淡的 currying / monad 唬住,这东西并不那么高大上。
纯函数,无论用什么语言的人每天都在写。需要用纯函数就写成纯的,不需要就用不纯的。全纯的系统,没什么鸟用。
CPU 就是有状态的,你用不用?硬盘和内存都是保存状态的,没有硬盘和内存的电脑,你要嘛?
不变性在现实世界根本不存在,这是物理的基本原理。太阳的位置不变?人的年龄不变?严格追求不变性的语言,根本无法描述现实世界。
惰性求值,让你的程序充满不确定性,如果一个变量惰性求值需要 30 秒,这样的东西能用嘛?
Currying 和 monad 是在解决函数式编程的特有问题,它那是腿瘸所以需要这玩艺,不瘸的人就算了。
并行,map, reduce 并不是函数式编程特有的。
用函数式编程开发并行程序,唯一比其它语言有利的地方,就是它确实强制去掉了循环、状态这些 “不利于并行的东西(但对程序是必不可少!)”。
那是,不管晴雨强制你每天带伞,即使你是住沙漠养骆驼的,当然淋不着。
引用来自“乌龟壳”的评论
Lisp Machine又是啥?cpu之所以“有状态”是因为不是使用lambda理论设计的,而是冯诺伊曼体系设计的。至于谁好谁不好没研究,不知道。
引用来自“kchr”的评论
我绝对支持在 Lisp Machine 上工作的人去玩符号逻辑。总之,函数式编程最初的目的,是运行在逻辑学家头脑里的一台有无限时间的 0 耗能理想机器上的,无限长纸带的图灵机啥的。模型要简单和基本,方便在大脑中运行,至于运行个 "Hello World" 就需要 1 亿年,那倒不是什么缺点。
很多关于关于函数式编程的优点的说法,基本都是扯淡。
至于并行,发掘待解决问题中并行性,主要取决于问题本身的性质,和函数式编程无关。
在我看来,函数式编程唯一的优点,就是强制程序员从一开始就发掘并行性 -- 即使在不需要它并行的地方。由于函数式程序非常缓慢,真要靠它并行的时候,又比用 C++ 搭配 TBB 写的并行程序慢很多 -- 那我要你还有鸟用?!
我只是个搬运工~~~
引用来自“crab2313”的评论
楼上几位的评论就跟“只学c的说oop没有任何鸟用就是层语法”这个说法如出一辙引用来自“kchr”的评论
不是。只是说函数式编程主要是设计给逻辑学家,研究计算的完备性等各种数学概念用的,各种特性有利于数学推理,不利于工程中使用。
可以学来玩玩,但不要被那些扯淡的 currying / monad 唬住,这东西并不那么高大上。
纯函数,无论用什么语言的人每天都在写。需要用纯函数就写成纯的,不需要就用不纯的。全纯的系统,没什么鸟用。
CPU 就是有状态的,你用不用?硬盘和内存都是保存状态的,没有硬盘和内存的电脑,你要嘛?
不变性在现实世界根本不存在,这是物理的基本原理。太阳的位置不变?人的年龄不变?严格追求不变性的语言,根本无法描述现实世界。
惰性求值,让你的程序充满不确定性,如果一个变量惰性求值需要 30 秒,这样的东西能用嘛?
Currying 和 monad 是在解决函数式编程的特有问题,它那是腿瘸所以需要这玩艺,不瘸的人就算了。
并行,map, reduce 并不是函数式编程特有的。
用函数式编程开发并行程序,唯一比其它语言有利的地方,就是它确实强制去掉了循环、状态这些 “不利于并行的东西(但对程序是必不可少!)”。
那是,不管晴雨强制你每天带伞,即使你是住沙漠养骆驼的,当然淋不着。
cpu之所以“有状态”是因为不是使用lambda理论设计的,而是冯诺伊曼体系设计的。至于谁好谁不好没研究,不知道。
其实代码就是信息,不管面向对象还是过程式还是函数式,在我眼里只关心能不能不要给我使绊子的前提下,让我清晰地把需要的逻辑记录下来,并能自动检查错误,自动生成最终需要运行的程序,就足够了。
动态语言如Python/PHP在自动错误检查方面不够方便
以前的java过于面向对象,在表达一些嵌套逻辑的时候不够方便,现在还行
函数式接触得不多,看了下schema的一些程序片段,基本能一眼看懂程序逻辑,应该还行吧。
我说这么多其实想表达的是,这没啥大惊小怪的,只不过是语法而已,再完美的语法也抵不住很少方便的库。
引用来自“kchr”的评论
还有,那他妈的是“代码简洁”吗?那是语言内置了库好不好。我还可以一行代码向网站发一条 url,就获取图像识别结果呢。
看到自己不识得的东西,不去了解就狂喷是不可取的,只会暴露智商。