函数式编程(0x1)

这个系列是记录一下学习函数式编程的心得,在网上搜索相关资源的时候,发现这玩意居然还是一门课,大概有10来个小时,所以一篇文章肯定是搞不定的。想着就搞一个系列,但是更新不会稳定,随缘吧。

忘记OOP

在武当山上,方东白化名阿大,手持倚天剑向张三丰挑战。

张三丰此时已经身受重伤,无力应战。张三丰将自己新创的太极剑法传给了张无忌。

张三丰传剑的过程,颇让人感到诧异。他不仅当众传剑,让方东白看得清清楚楚,而且招数慢吞吞,软绵绵,竟让众人以为张三丰有意放慢了招数,好让张无忌瞧得明白。张三丰共使了两遍剑法,第二次所使,和第一次使的竟然没一招相同。最后张三丰让张无忌将剑招忘得干干净净,才叫他与方东白比试。

张三丰传剑大违常理,这让周颠等人很是担心。没想到张无忌一出手竟大奏奇效,彻底击败了方东白,并以木剑斩下他的一条手臂。

忘记剑招是学习太极剑的关键,同理忘记OOP是学习函数式编程的关键。

我们先来看一个程序的组成:

我们可以将程序分为2个部分:

上面的 classes,methods,inheritance 等就是OOP的重要组成部分。

函数式编程就相当于是对programs的组成进行一个重构。

重构应该绝大部分人都经历过了,假设我们有一个业务,刚开始每个模块都工作的非常好:

随着需求的迭代,我们需要新增一些模块,删除一些模块:

甚至开始在模块之间添加依赖关系:

现在,我们的程序还是能够工作,但是一言难尽,特别是修bug,可能涉及到很多的地方。这个时候应该怎么做呢?如果你非常的幸运,你/你的领导会想,维护这坨屎,还不如搞个新的:

但是,真的需要从0开始吗?没有人喜欢从0写一套新的东西,一般我们都会审视旧的模块,有的写的好,有的写的差,我们会将好的模块留下来,坏的丢弃。

所以重构的核心,在于重新组织现有的好的模块,然后添加新的部分来让这些模块好好工作。比如说,添加一个总线:

现在回到我们套路的问题,为啥我们需要忘记OOP,其中的一点就是OOP有点太过了(当然对于大多数人来说都可能无法体会),就拿泛型的类型擦除来说,就很奇葩,类似的还有C++的友元函数,规则复杂的一逼。这是一种预兆,OOP开始束缚我们的脚步了,它变的有点混乱了。

我们就需要像重构业务一样,重构我们的程序的组成部分:

什么是 f(x)

f(x) 是从数学里面借过来的一个概念。比如:f(x) = x * 2 ,这个函数就是将任意的输入 x,变成输出 x * 2。

在函数里面,一个重要的特征就是,我们放一个东西进去,然后它给我们一个结果,没有其他任何的多余动作。

假设我们将函数看成一个黑盒子,那么它就是这样的:

这个黑盒子在工作的时候,不会对外界产生任何影响,也就是我们常说的没有副作用(No Side Effects)。

看一个例子:

ini 复制代码
x = ['a', 'b', 'c']
y = a(x)
x = ?

在这个例子中,x在函数执行后是多少呢?当然还是x原来的值,它不会变化。函数没有副作用不仅仅是不会影响外界,而且连参数都不会改变。

再看这个:

x还是原来的值!!!

这就带来一个问题,比如在 java 里面,x 是一个数组:

我们的函数需要改变第2个位置的值,假设我们的函数如下:

那么,这就改变了参数的值,与函数的定义违背了。解决办法就是将参数的值 copy 一份:

那么这又带来一个问题,我们可能需要拷贝很多很多份数据:

解决办法是我们需要设计新的数据结构,比如将数组变成链表,举个例子:

当 n 变化的时候,我们只需要拷贝3个节点,其余的可以复用。

说的有点远了,我们回到函数编程上来。有了函数和不变性数据结构,我们就创建了一个纯净且完美的世界,我们的数据不怕被篡改,也没有任何副作用。但是又出问题了,没有副作用才是最大的问题。

什么是副作用?写文件,输出到控制台,操作数据库等等都是副作用,用户是把副作用当作程序的核心,用户不会关心我们的代码,他们需要的就是副作用。

一个没有副作用的程序只是一个空中楼阁,很美好很纯净,但是啥都不能做。这就有问题了,所以我们需要一个桥梁,链接我们的美好世界到外部的纷争世界:

桥梁

实现通往外部的桥梁有很多问题需要解决,其中之一在于,如何改变程序中的状态?举个例子:以一个website,它记录了访问次数,这个功能该如何使用函数实现?

假设我们有这样的一个结构,Atoms:

Atoms 是一个可变状态的容器。那么这个时候就有人要问了:这不就是一个变量吗?有啥区别?

它与变量的不同之处在于更新自身的方式不一样。

因为Atoms 是一个桥梁,所以,我们只需要给他一个函数就ok了:

你可能觉得这没有什么大不了的,我搞个变量,应用这个函数不也一样。那么我们再深入一下,我们有两个线程来更新这个 Atoms:

这个时候就需要处理同步问题,按照我们一般的写法,甚至需要给 f(x) g(x) 进行加锁。但是函数式写法不需要,一个值得注意的点是函数是没有副作用的,而且输入不变的情况下输出不变,所以我们重复执行函数是没有任何问题的。

意识到这点,我们就可以将同步的处理限制在 Atoms 里面。假设当 f(x) g(x) 同时更新,g(x) 先执行了,那么 f(x) 再继续更新的时候,Atoms 会注意到这个行为,它就会让 f(x) 获取到最新的值,重复执行一次即可。

结尾

刚开始解除函数式编程的时候,写起代码来可能会束手束脚,就感觉自己带了一个手铐一样,但是当你有了一定的积累之后,就会发现手铐变成了自行车车把,虽然你必须将手放在车把上面,但是你达到目的地的时间会缩短。

其实函数式编程与OOP,我个人没啥偏向,看起来它们是背道而驰:

但是kotlin刚好两者都有,或许你自己可以在其中达到一个平衡。

希望通过上面的讲解,能够让你对函数式编程有一定的理解。它的3个重要部分:

  • 函数
  • 不变性
  • 桥梁
相关推荐
m0_748240251 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar1 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人2 小时前
前端知识补充—CSS
前端·css
GISer_Jing2 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245522 小时前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v2 小时前
webpack最基础的配置
前端·webpack·node.js
pubuzhixing2 小时前
开源白板新方案:Plait 同时支持 Angular 和 React 啦!
前端·开源·github
2401_857600952 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_857600952 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL2 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js