首页>>前端>>Node->彻底搞懂Functional Programming(一)

彻底搞懂Functional Programming(一)

时间:2023-11-29 本站 点击:0

highlight: agate theme: cyanosis

Functional Programming 思维

Functional Programming (简称 FP),是一种撰写风格,我觉得更像是一种抽象化的思维,因为用这种思维下去写的 code,会以function为操作的主体。

我觉得 FP 有点像是生产线的思维,第一个员工专门负责备料,第二个员工会接着用这些材料制作成料理,第三个员工就把这些料理送到客人手上。

而每一个员工都像是一个 function,它们不管其它的,只管自己手上的任务,它们的台词可能是像这样:

「我就只会切菜,我就专门切菜,切菜以外的我都不管」

「只要上一个员工有给我菜,我一定会把它切好传给下一个员工」

「我不会去影响其他员工,连聊天都不会」

让大家对 FP 有个超级基础的概念,但今天还不会真的写到 code,因为我们要先来了解很多 FP 的名词跟观念:

First-class (一等公民)

要写 FP 的首要条件就是,function 必须是这个语言的一等公民,代表跟其他资料型别具有同等地位,也就是要拥有这些特性:

function 可以用来赋值给变数

const addNum = (x, y) => x + y;

function 能被当作参数传入

arr.map(num => num + 1);

function 能被当作回传值

const addNum = (x) => {  return (y) => {    return x + y;  };};

这是硬条件哦!少了这个就不能叫 FP 了。

Higher-order Functions (高阶函式)

以下两者符合一项,即是高阶函式 (简称 HoF):

可以将函式当成参数传入另一个函式

可以将函式当成另一个函式的回传值

虽然我们一般写 function,会拿来当参数的,大部分都是 string、array 或 object 之类的。

但仔细一想却会发现:

arr.forEach(() => {});arr.map(() => {});arr.filter(() => {});arr.reduce(() => {}, initialValue);

啊啊啊原来到处都在拿 function 当参数啊!虽然也没有很普遍,但起码不算陌生。不过。。。

HoF 带来了什么好处?

比起 HoF 「是什么」,网路上反而很少在讨论「为什么」要有 HoF?它给 FP 带来了什么好处? 其实我还真不知道,于是我也乖乖去 google 了一下:

functional programming higher order functions "why"

没错我还特地把 why 用双引号框起来,才比较找得到

得到一个最重要的结论是,HoF 让程式可以比较容易「抽象化」。

我试着讲讲我的理解,这边很期待能有朋友一起补充。

比较 HoF 的 before & after

比如我们熟悉的 filter 就是 HoF,那如果在没有 HoF、没有 filter 的情况下,我们要怎么做到「筛选」这件事呢:

// 筛选出 10 以下的数字const arr = [3, 6, 9, 12, 15];const lessThanTen = [];for (let i=0; i<arr.length; i++) {    if (arr[i] < 10) {        lessThanTen.push(arr[i]);    }}console.log(lessThanTen);执行结果[3, 6, 9]那现在使用 HoF 来做,会变成:// 筛选出 10 以下的数字const arr = [3, 6, 9, 12, 15];const lessThanTen = arr.filter(num => num < 10);console.log(lessThanTen);执行结果[3, 6, 9]

OK,先不要把重点放在 filter 的 code 比较少这件事,因为如果把 filter 底层的 code 翻出来,执行的量绝对不会少于上面的 for 回圈。

重点在于,我们抽象化了「筛选」这个动作。

我们抽象化了「筛选」这个动作。

抽象化了「筛选」。

系统提示:你看到脑中的回音了

抽象化的意义

抽象化并不是把 for 回圈拉出去当 function 那么简单: 如果我今天需要筛选的是

5 以下的数字

乘以 3 是 2 的倍数的数字

与今天月份相同的数字

我是不是还要为了这几个特别的 case,又多写三个 function 出来?

听起来就很难维护啊!所以我想抽象化就是为了解决这个问题,可能称作「客制化」的问题吧!

我们把筛选这个动作抽象化,所有想要做「筛选」动作的,都可以呼叫filter,然后根据你的需求,把判断用的 function 丢进参数,就完成一个「客制」的 filter 了。

const arr = [3, 6, 9, 12, 15];// 筛选出 5 以下的数字arr.filter(num => num < 5);// 乘以 3 是 2 的倍数的数字arr.filter(num => (num * 3) % 2 === 0);// 与今天月份相同的数字arr.filter(num => num === new Date().getMonth() + 1);执行结果[3][6, 12][9]

所以或许可以这样说,HoF 能够赋予 function 在某个基础上客制化的能力

回传函式的 HoF

比如我们自己来写一个,回传 function 的 HoF:

const addNum = (x) => {  return (y) => {    return x + y;  };};// 可简化成// const addNum = x => y => x + y;const addFive = addNum(5);const addTen = addNum(10);addFive(3); // 8addTen(3); // 13

有感受到了吗?透过 addNum 这个 HoF,我们可以很快「客制」出两个额外的 function,分别处理 + 5 与 + 10 的 case,这是抽象化非常厉害的地方呢!

Pure Functions (纯函式)

关于纯函式的定义,在维基可以看到比较精准的定义:

函式与外界交换资料只有一个唯一渠道—— 参数和回传值

函式从函式外部接受的所有输入资讯,都通过参数传递到该函式内部

函式输出到函式外部的所有资讯,都通过回传值传递到该函式外部

白话一点:

在函式内出现的变数,要嘛是函数内自己宣告的,要嘛是从参数传进来的,有其他来源的话就是 impure

而 impure 的函式,就代表函式里面有 side effects。

Side Effects

side effects 我们有在彻底掌握 Function (一)提到过,如字面上的意思就是副作用,翻成白话应该是:「你做的事影响到其它人」。

常见的 side effects 如下:

发送 http request (如fetchaxios)

在画面印出值或是 log (如console.log)

操作 DOM 物件 (如docuement.querySelector)

我想许多人会感到困惑的应该是这个吧。。。

console.log怎么也算 side effects 啊!它招谁惹谁了 QQ 把东西印出来又不会出事!

这部分算是我也还在理解的,我想是因为 pure function 要的是完全的纯粹,也就是这个 function 里面只要做好它「该做的事」。

而像 console.log 这样其实是去呼叫 window.console.log 的指令,一来它「不是该做的事」,二来它就是「影响到别人了」。

100% 的纯度?

这边需要强调一点,不用强硬追求 100% 的 pure,或者 100% 没有 side effects,因为如果真的达到 100% 了,是不是也不能够发送 http request 跟操作 DOM 了呢?

我认为要追求的是,尽可能让有 side effects 的程式码被集中 (共用),不要东一个西一个,才能够将测试时的负担降到最低。

Pure 追求的不是

zero side effects

而是

minimize side effects

结语


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Node/889.html