[一点总结]函数式编程入门 -- Curry, Compose

准备给TWU的同学做一个关于函数式的Session,昨天在准备Slides,顺手总结一下,有正在学习的小伙伴可以做一个参考。

What

Functional Programming(函数式编程)在概念上和Object Oriented Programming(面向对象编程), Procedural Programming(过程化编程)类似, 是一种编程范式。
与OOP以对象为中心的理念不同,FP将所有计算机的操作视为函数运算,函数是操作的基本单位。函数拥有和基本类型一样的地位,可以将一个变量赋值为函数(First class -- 一等公民),可以在函数的参数中传递函数(higher-order function -- 高阶函数)

Why

  1. 学习一点新的编程范式可以有效防止老年痴呆。
  2. 真的很有趣
  3. 相比于过程化、面向对象,函数式书写的代码更易读,更简短。
  4. 因为函数式编程是无副作用(side effects)的,不需要考虑死锁问题,适合并发编程,因此在云计算领域得到了广泛应用(Scala)

How

好了,进入正题
以下示例代码均为JavaScript

1. 副作用--Side Effects

先来看两段代码

//代码片段1
let minium = 20;
const checkAge = (age)=> age >= minium;
//代码片段2
let number = 2;
const multipleNumber = (n) => {
  number = number * n;
  return number;
}

这两段代码有问题吗?
通常情况下,代码片段1并不会发生什么问题, 我们传入年龄,并且判断是不是大于20岁。

但如果有人修改了minium呢?此时判断的条件改变了,导致我们的结果也会改变。当我们第二次运行checkAge(22)的时候,可能返回的并不是第一次运行的结果。

对于checkAge这个函数来说,它需要观测的值不仅有入参age,还有一个全局变量minium,它的运行结果依赖系统状态,这对于程序员来说是十分痛苦的。

而代码片段2就很容易发现问题了,这个函数修改了一个全局变量,换言之,它修改了系统状态,当第二次输入相同参数的时候你会得到一个不一样的结果。

不,这太让人难过了,这不是我们想要的,我们希望我们的函数足够纯净,相同的输入永远得到相同的输出。而且,不要做多余的事:
偷偷在console里打一个log
偷偷给某个api发送一个request
偷偷修改本地文件系统

2. 纯函数--Pure Function

Side Effects好吗?我们心里都知道不好,但有的时候你不得不接受它,就像生活。
或许你的系统需要维护某些状态来运行不同的程序,和它们接触的函数就不可避免的变得不纯净起来。但这并不意味着我们就放弃治疗了,把怪兽关在壁橱里总比把它放出来到处搞破坏来得好。正所谓关注分离。

是时候引入纯函数了。
我们把上面的代码修改成这样如何:

//代码片段3
const checkAge = (age) => { const minium = 20; return age >= 20;}
//代码片段4
const multipleNumber = (number, n) => {return number * n};

对于代码片段3来说,现在它终于不用始终看着minium了,现在minium变成了它的一部分,永远不会改变。这样我们相同的输入,永远都会有相同的输出。

对于代码片段4,我们可能会有疑问:“原来我只用输入一个参数,现在我要输入两个参数了,有点不太对劲啊?”
“Emmmmm, 至少我们可以通过始终调用multipleNumber(2,n)或者给number一个默认值来让它变得纯函数起来const multipleNumber(n,number=2)

所以纯函数是什么呢?
Stateless(不改变状态,也不被状态影响)

3. 柯里化 -- Curry

让我们稍稍改变一下代码片段4,这次让我们消除疑虑。

//代码片段5
const multipleNumber = (number) => (n) => {return number * n}

“教练,有什么区别啊?我还是得传两个参数啊,唯一的区别是我得写两对括号了啊(multipleNumber(2)(n)!!!”

哎呀别急,回头看看文章开头,"函数拥有和基本类型一样的地位,可以将一个变量赋值为函数(First class -- 一等公民),可以在函数的参数中传递函数(higher-order function -- 高阶函数)。"

再看看代码片段5,看到两个=>有没有想起什么?

我们有一整页的时间思考
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……
……

答案是:
我们现在可以写这样的代码了:

const multipleTwo = multipleNumber(2); // (n) => {return 2*n}
const result = multipleTwo(3);//6

我们把这样拆分函数参数 --> 形成一个高阶函数 的过程,称为柯里化。
这就是一个最简单的柯里化的例子。
好处?

生成的高阶函数变成了一个工厂,用来生产诸如multipleTwo这样的函数。
有效消除重复代码,谁用谁知道。

再举一个例子:

const matchRegex = (regex, str) => str.match(regex);

将其柯里化之后得到一个叫matchRegex的工厂

const matchRegex = (regex) => (str) => str.match(regex);

我们可以生产这样的函数:

const hasSpace = match(/\s+\g);

对,matchRegex是没有上下文含义的,但生成的函数根据传入的正则产生了不同的业务含义。

*只传给函数一部分参数也叫局部调用,当然 和柯里化是完全不同的两个东西

4. 组合 -- Compose

现在相信我们已经对函数式编程有了一个初步的认知。

想象这样一个业务场景:
我们是Email Writer,
有时候我们需要把邮件的某些Text变成UPPERCASE,
const uppercase = (str) => str.toUpperCase();
有时候我们需要给Text末尾加上一个感叹号,!
const addBondToEnd = (str) => str+"!";
有时候我们需要既把Text变成uppercase, 也要在后面加一个感叹号:TEXT!

怎么办呢?

//So Easy
const addBondToEndAndUpperCase=(str)=>{
  return addBondToEnd(uppercase(str));
};

把两个函数都调用一遍不就好了。我们还生成了一个新函数,每次想做这两个操作的时候调用这个新的函数就行了。

真棒!我们已经知道组合是什么东西了!

但多想一步,如果我们有许许多多这样的基础函数大概10000个,我们想要生成不同的组合函数,我们还要这样手写新函数吗?

我太懒了,我不想这么做,重复写这样的code让我觉得生不如死。

让我们回到文章开头,"函数拥有和基本类型一样的地位,可以将一个变量赋值为函数(First class -- 一等公民),可以在函数的参数中传递函数(higher-order function -- 高阶函数)。"

这次直接揭晓答案吧:
对于我这样的懒人,需要的是这样一个工具

const compose = (f, g) => (x) => f(g(x));

这样我就可以这样调用了:

const uppercase = (str) => str.toUpperCase();
const addBondToEnd = (str) => str+"!";
//addBondToEndAndUpperCase
compose(addBondToEnd, uppercase)("text");//TEXT!
//Of course you can assign it to a variable
const addBondToEndAndUpperCase = compose(addBondToEnd, uppercase)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容