函数式编程(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个重要部分:

  • 函数
  • 不变性
  • 桥梁
相关推荐
定栓8 分钟前
Webpack迁到Vite第一节:模块系统迁移:require → import
运维·前端·webpack
小熊在忙fpga17 分钟前
N32G430芯片的flash表示
前端·javascript·html
Data analyse45622 分钟前
App埋点版本兼容的字段迁移校验
大数据·运维·服务器·前端·数据库·数据分析
todoitbo29 分钟前
本来只想给内网开个口子,Natapp 却多给了一道防护
前端·javascript·vue.js·内网穿透·网络防护·natapp
会灭火的程序员34 分钟前
SuperMap AgentX Server智能体接入WEB应用技术文档
前端·supermap·agentx·agentx server
CRMEB39 分钟前
商品 SKU 设计,从简单规格到多规格踩过什么
java·前端·数据库·开源
Chloe.Zz40 分钟前
一篇入门Agent
开发语言·前端·javascript
java1234_小锋1 小时前
【技术专题】HTML5 网页技术 - HTML5 表格标签
前端·html·html5
雪芽蓝域zzs1 小时前
JS 项目转 TS 项目(Vue3 + 若依这类后台项目)完整改造清单
前端·vue.js