本文要回答一个很重要的问题:函数式编程有什么用?
目前,主流的编程语言都不是函数式的,已经能够满足需求。为何还要学函数式编程呢,只为了多理解一些新奇的概念?
一个网友说:
"函数式编程有什么优势呢?"
"我感觉,这种写法可能会令人头痛吧。"
很长一段时间,我根本不知道从何入手,如何将它用于实际项目?直到有一天,我学到了 Pointfree 这个概念,顿时豁然开朗,原来应该这样用!
我现在觉得,Pointfree 就是如何使用函数式编程的答案。
为了理解 Pointfree,请大家先想一想,什么是程序?
上图是一个编程任务,左侧是数据输入(input),中间是一系列的运算步骤,对数据进行加工,右侧是最后的数据输出(output)。一个或多个这样的任务,就组成了程序。
输入和输出(统称为 I/O)与键盘、屏幕、文件、数据库等相关,这些跟本文无关。这里的关键是,中间的运算部分不能有 I/O 操作,应该是纯运算,即通过纯粹的数学运算来求值。否则,就应该拆分出另一个任务。
I/O 操作往往有现成命令,大多数时候,编程主要就是写中间的那部分运算逻辑。现在,主流写法是过程式编程和面向对象编程,但是我觉得,最合适纯运算的是函数式编程。
上面那张图中,运算过程可以用一个函数fn
表示。
fn
的类型如下。
fn :: a -> b
上面的式子表示,函数fn
的输入是数据a
,输出是数据b
。
如果运算比较复杂,通常需要将fn
拆分成多个函数。
f1
、f2
、f3
的类型如下。
f1 :: a -> m
f2 :: m -> n
f3 :: n -> b
上面的式子中,输入的数据还是a
,输出的数据还是b
,但是多了两个中间值m
和n
。
我们可以把整个运算过程,想象成一根水管(pipe),数据从这头进去,那头出来。
函数的拆分,无非就是将一根水管拆成了三根。
进去的数据还是a
,出来的数据还是b
。fn
与f1
、f2
、f3
的关系如下。
fn = R.pipe(f1, f2, f3);
上面代码中,我用到了 Ramda 函数库的pipe
方法,将三个函数合成为一个。Ramda 是一个非常有用的库,后面的例子都会使用它,如果你还不了解,可以先读一下教程。
fn = R.pipe(f1, f2, f3);
这个公式说明,如果先定义f1
、f2
、f3
,就可以算出fn
。整个过程,根本不需要知道a
或b
。
也就是说,我们完全可以把数据处理的过程,定义成一种与参数无关的合成运算。不需要用到代表数据的那个参数,只要把一些简单的运算步骤合成在一起即可。
这就叫做 Pointfree:不使用所要处理的值,只合成运算过程。中文可以译作"无值"风格。
请看下面的例子。
var addOne = x => x + 1;
var square = x => x * x;
上面是两个简单函数addOne
和square
。
把它们合成一个运算。
var addOneThenSquare = R.pipe(addOne, square);
addOneThenSquare(2) // 9
上面代码中,addOneThenSquare
是一个合成函数。定义它的时候,根本不需要提到要处理的值,这就是 Pointfree。
Pointfree 的本质就是使用一些通用的函数,组合出各种复杂运算。上层运算不要直接操作数据,而是通过底层函数去处理。这就要求,将一些常用的操作封装成函数。
比如,读取对象的role
属性,不要直接写成obj.role
,而是要把这个操作封装成函数。
var prop = (p, obj) => obj[p];
var propRole = R.curry(prop)('role');
上面代码中,prop
函数封装了读取操作。它需要两个参数p
(属性名)和obj
(对象)。这时,要把数据obj
要放在最后一个参数,这是为了方便柯里化。函数propRole
则是指定读取role
属性,下面是它的用法(查看完整代码)。
var isWorker = s => s === 'worker';
var getWorkers = R.filter(R.pipe(propRole, isWorker));
var data = [
{name: '张三', role: 'worker'},
{name: '李四', role: 'worker'},
{name: '王五', role: 'manager'},
];
getWorkers(data)
// [
// {"name": "张三", "role": "worker"},
// {"name": "李四", "role": "worker"}
// ]
上面代码中,data
是传入的值,getWorkers
是处理这个值的函数。定义getWorkers
的时候,完全没有提到data
,这就是 Pointfree。
简单说,Pointfree 就是运算过程抽象化,处理一个值,但是不提到这个值。这样做有很多好处,它能够让代码更清晰和简练,更符合语义,更容易复用,测试也变得轻而易举。
下面,我们来看一个示例。
var str = 'Lorem ipsum dolor sit amet consectetur adipiscing elit';
上面是一个字符串,请问其中最长的单词有多少个字符?
我们先定义一些基本运算。
// 以空格分割单词
var splitBySpace = s => s.split(' ');
// 每个单词的长度
var getLength = w => w.length;
// 词的数组转换成长度的数组
var getLengthArr = arr => R.map(getLength, arr);
// 返回较大的数字
var getBiggerNumber = (a, b) => a > b ? a : b;
// 返回最大的一个数字
var findBiggestNumber =
arr => R.reduce(getBiggerNumber, 0, arr);
然后,把基本运算合成为一个函数(查看完整代码)。
var getLongestWordLength = R.pipe(
splitBySpace,
getLengthArr,
findBiggestNumber
);
getLongestWordLength(str) // 11
可以看到,整个运算由三个步骤构成,每个步骤都有语义化的名称,非常的清晰。这就是 Pointfree 风格的优势。
Ramda 提供了很多现成的方法,可以直接使用这些方法,省得自己定义一些常用函数(查看完整代码)。
// 上面代码的另一种写法
var getLongestWordLength = R.pipe(
R.split(' '),
R.map(R.length),
R.reduce(R.max, 0)
);
最后,看一个实战的例子,拷贝自 Scott Sauyet 的文章《Favoring Curry》。那篇文章能帮助你深入理解柯里化,强烈推荐阅读。
下面是一段服务器返回的 JSON 数据。
现在要求是,找到用户 Scott 的所有未完成任务,并按到期日期升序排列。
过程式编程的代码如下(查看完整代码)。
上面代码不易读,出错的可能性很大。
现在使用 Pointfree 风格改写(查看完整代码)。
var getIncompleteTaskSummaries = function(membername) {
return fetchData()
.then(R.prop('tasks'))
.then(R.filter(R.propEq('username', membername)))
.then(R.reject(R.propEq('complete', true)))
.then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
.then(R.sortBy(R.prop('dueDate')));
};
上面代码已经清晰很多了。
另一种写法是,把各个then
里面的函数合成起来(查看完整代码)。
// 提取 tasks 属性
var SelectTasks = R.prop('tasks');
// 过滤出指定的用户
var filterMember = member => R.filter(
R.propEq('username', member)
);
// 排除已经完成的任务
var excludeCompletedTasks = R.reject(R.propEq('complete', true));
// 选取指定属性
var selectFields = R.map(
R.pick(['id', 'dueDate', 'title', 'priority'])
);
// 按照到期日期排序
var sortByDueDate = R.sortBy(R.prop('dueDate'));
// 合成函数
var getIncompleteTaskSummaries = function(membername) {
return fetchData().then(
R.pipe(
SelectTasks,
filterMember(membername),
excludeCompletedTasks,
selectFields,
sortByDueDate,
)
);
};
上面的代码跟过程式的写法一比较,孰优孰劣一目了然。
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为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 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。