函数式编程的历史已经很悠久了,但是最近几年却频繁的出现在大众的视野,很多不支持函数式编程的语言也在积极加入闭包,匿名函数等非常典型的函数式编程特性。大量的前端框架也标榜自己使用了函数式编程的特性,好像一旦跟函数式编程沾边,就很高大上一样,而且还有一些专门针对函数式编程的框架和库,比如:RxJS、cycleJS、ramdaJS、lodashJS、underscoreJS 等。函数式编程变得越来越流行,掌握这种编程范式对书写高质量和易于维护的代码都大有好处,所以我们有必要掌握它。
维基百科定义:函数式编程(英语:functional programming),又称泛函编程,是一种编程范式,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。
在初中数学里,函数 f 的定义是:对于输入 x 产生一个唯一输出 y=f(x)。这便是纯函数。它符合两个条件:1.此函数在相同的输入值时,总是产生相同的输出。函数的输出和当前运行环境的上下文状态无关。2.此函数运行过程不影响运行环境,也就是无副作用(如触发事件、发起 http 请求、打印/log 等)。简单来说,也就是当一个函数的输出不受外部环境影响,同时也不影响外部环境时,该函数就是纯函数,也就是它只关注逻辑运算和数学运算,同一个输入总得到同一个输出。javascript 内置函数有不少纯函数,也有不少非纯函数。
纯函数:Array.prototype.sliceArray.prototype.mapString.prototype.toUpperCase
非纯函数:Math.randomDate.nowArray.ptototype.splice
这里我们以 slice 和 splice 方法举例:
var xs = [1,2,3,4,5];
// 纯的
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
// 不纯的
xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []
我们看到调用数组的 slice 方法每次返回的结果完全相同,同时 xs 不会被改变,而调用 splice 方法每次返回值都不一样,同时 xs 变得面目全非。这就是我们强调使用纯函数的原因,因为纯函数相对于非纯函数来说,在可缓存性、可移植性、可测试性以及并行计算方面都有着巨大的优势。这里我们以可缓存性举例:
var squareNumber = memoize(function(x){ return x*x; });
squareNumber(4);
//=> 16
squareNumber(4); // 从缓存中读取输入值为 4 的结果
//=> 16
那我们如何把一个非纯函数变纯呢?比如下面这个函数:
var minimum = 21;
var checkAge = function(age) {
return age >= minimum;
};
这个函数的返回值依赖于可变变量 minimum 的值,它依赖于系统状态。在大型系统中,这种对于外部状态的依赖是造成系统复杂性大大提高的主要原因。
var checkAge = function(age) {
var minimum = 21;
return age >= minimum;
};
通过改造,我们把 checkAge 变成了一个纯函数,它不依赖于系统状态,但是 minimum 是通过硬编码的方式定义的,这限制了函数的扩展性,我们可以在后面的柯里化中看到如何优雅的使用函数式解决这个问题。所以把一个函数变纯的基本手段是不要依赖系统状态。
curry 的概念很简单:将一个低阶函数转换为高阶函数的过程就叫柯里化。 用一个形象的比喻就是: 比如对于加法操作:var add = (x, y) => x + y,我们可以这样柯里化:
//es5写法
var add = function(x) {
return function(y) {
return x + y;
};
};
//es6写法
var add = x => (y => x + y);
//试试看
var increment = add(1);
var addTen = add(10);
increment(2); // 3
addTen(2); // 12
对于加法这种极其简单的函数来说,柯里化并没有什么用。还记得上面的 checkAge 函数吗?我们可以这样柯里化它:
var checkage = min => (age => age > min);
var checkage18 = checkage(18);
checkage18(20);
// =>true
这表明函数柯里化是一种“预加载”函数的能力,通过传递一到两个参数调用函数,就能得到一个记住了这些参数的新函数。从某种意义上来讲,这是一种对参数的缓存,是一种非常高效的编写函数的方法:
var curry = require('lodash').curry;
//柯里化两个纯函数
var match = curry((what, str) => str.match(what));
var filter = curry((f, ary) => ary.filter(f));
//判断字符串里有没有空格
var hasSpaces = match(/\s+/g);
hasSpaces("hello world"); // [ ' ' ]
hasSpaces("spaceless"); // null
var findSpaces = filter(hasSpaces);
findSpaces(["tori_spelling", "tori amos"]); // ["tori amos"]
假设我们需要对一个字符串做一些列操作,如下,为了方便举例,我们只对一个字符串做两种操作,我们定义了一个新函数 shout,先调用 toUpperCase,然后把返回值传给 exclaim 函数,这样做有什么不好呢?不优雅,如果做得事情一多,嵌套的函数会非常深,而且代码是由内往外执行,不直观,我们希望代码从右往左执行,这个时候我们就得使用组合。
var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = function(x){
return exclaim(toUpperCase(x));
};
shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
使用组合,我们可以这样定义我们的 shout 函数:
//定义compose
var compose = (...args) => x => args.reduceRight((value, item) => item(value), x);
var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);
shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
代码从右往左执行,非常清晰明了,一目了然。我们定义的 compose 像 N 面胶一样,可以将任意多个纯函数结合到一起。这种灵活的组合可以让我们像拼积木一样来组合函数式的代码:
var head = function(x) { return x[0]; };
var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
var last = compose(head, reverse);
last(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'uppercut'
命令式代码:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。声明式代码:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。与命令式不同,声明式意味着我们要写表达式,而不是一步一步的指示。以 SQL 为例,它就没有“先做这个,再做那个”的命令,有的只是一个指明我们想要从数据库取什么数据的表达式。至于如何取数据则是由它自己决定的。以后数据库升级也好,SQL 引擎优化也好,根本不需要更改查询语句。这是因为,有多种方式解析一个表达式并得到相同的结果。这里为了方便理解,我们来看一个例子:
// 命令式
var makes = [];
for (var i = 0; i < cars.length; i++) {
makes.push(cars[i].make);
}
// 声明式
var makes = cars.map(function(car){ return car.make; });
命令式的循环要求你必须先实例化一个数组,而且执行完这个实例化语句之后,解释器才继续执行后面的代码。然后再直接迭代 cars 列表,手动增加计数器,就像你开了一辆零部件全部暴露在外的汽车一样。这不是优雅的程序员应该做的。声明式的写法是一个表达式,如何进行计数器迭代,返回的数组如何收集,这些细节都隐藏了起来。它指明的是做什么,而不是怎么做。除了更加清晰和简洁之外,map 函数还可以进一步独立优化,甚至用解释器内置的速度极快的 map 函数,这么一来我们主要的业务代码就无须改动了。函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。相反,不纯的不函数式的代码会产生副作用或者依赖外部系统环境,使用它们的时候总是要考虑这些不干净的副作用。在复杂的系统中,这对于程序员的心智来说是极大的负担。
pointfree 模式指的是,永远不必说出你的数据。它的意思是说,函数无须提及将要操作的数据是什么样的。一等公民的函数、柯里化(curry)以及组合协作起来非常有助于实现这种模式。
// 非 pointfree,因为提到了数据:word
var snakeCase = function (word) {
return word.toLowerCase().replace(/\s+/ig, '_');
};
// pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);
这种风格能够帮助我们减少不必要的命名,让代码保持简洁和通用。当然,为了在一些函数中写出 Point Free 的风格,在代码的其它地方必然是不那么 Point Free 的,这个地方需要自己取舍。
拥有了以上的知识,我们是时候该写一个示例应用了。这里我们使用了 ramda ,没有用 lodash 或者其他类库。ramda 提供了 compose、curry 等很多函数。我们的应用将做四件事:1.根据特定搜索关键字构造 url 2.向 flickr 发送 api 请求 3.把返回的 json 转为 html 图片 4.把图片放到屏幕上上面提到了两个不纯的动作,即从 flickr 的 api 获取数据和在屏幕上放置图片这两件事。我们先来定义这两个动作,这样就能隔离它们了。这里我们只是简单包装了一下 jQuery 的 getJSON 函数,把它变为一个 curry 函数,还有就是把参数位置也调换了下,我们把它们放在 Impure 命名空间下以用来隔离,这样我们就知道它们都是危险函数。运用函数柯里化和函数组合的技巧,我们就可以创建一个函数式的实际应用了:
预览地址:demo[1]看看,多么美妙的声明式规范啊,只说做什么,不说怎么做。现在我们可以把每一行代码都视作一个等式,变量名所代表的属性就是等式的含义。
我们已经见识到如何在一个小而不失真实的应用中运用新技能了,但是异常处理以及代码分支呢?如何让整个应用都是函数式的,而不仅仅是把破坏性的函数放到命名空间下?如何让应用更安全更富有表现力?我会在下一篇文章中介绍函数式编程的更加高阶一些的知识,例如 Functor、Monad、Applicative 等概念。
[1]预览地址:demo: https://code.h5jun.com/vixe/1/edit?html,js,output
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/32DwoU2OtAcYPpTQAo6npA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。