阅读 649

《Functional Programming in 40 minutes》笔记

为什么学这个

本文是 状态管理框架 选型预研时看到油管上一个Talk的部分内容的总结和一些自己的思考。

要引入一个框架,从上到下的过程包括

  • API的使用 (调用)
  • API的设计 (接口)
  • API的实现 (源码)
  • API实现的是哪个思想 (原理)

状态管理框架 可追根溯源到函数式的本质特性上,所以有必要先搞清楚函数式为何物。

而一个“新”事物的流行往往会产生泡沫,所以也会有人把函数式当成 银弹 ,如同对待 区块链人工智能大数据 一样。在这里希望一方面把函数式的来龙去脉说清,另一方面戳破这些浮夸的 泡沫效应 ,让它回归到原有的价值。

函数式从何而来

函数式的历史可以追述到1930年代的{lambda calculus},函数式编程本质上就是对 λ演算 的一种实现。基本思想就是只通过“变量”和“函数”两个元素来解决问题。这个思想给函数式编程带来诸如 匿名函数一等公民柯里化 等概念。

我们所接触到的主流编程语言几乎都是 图灵机 的思想延续,可见这一分支一直是处于绝对统治地位。但随着现代语言越来越多的加入 λ演算 的特性,这两个思想最终产生了交集。

那么函数式思想为什么从一个小众的事物慢慢变成了显学了呢?那么我们看一下这个思想到底给软件工程注入了哪些新鲜血液。

图灵系语言的问题

有一种误解是学习函数式要忘记掉你所学过的编程方式,但与其说引入函数式就是要用它替代 面向对象 ,还不如说是对 面向对象 的程序进行重构,毕竟两者各有优劣。

我们一开始满足某个业务写的 面向对象 代码可能是以下的样子,你可以把他看做一些页面,函数或是模块。

随时业务的变化,可能有些组件被删掉,或是新增了新的模块,也可能是模块间的调用/依赖关系有了变化。于是复杂度开始增大。

代码变成这个样子后,虽然表面上仍然可以使用,但是源码却已经难以理解和维护 我们会发现难以理解的一个原因是:这个系统一件事的成立,需要大量的前提! ++因为依赖太多++

图灵系的语言也有一样的困境,随着语言的迭代更新,逐渐加入了更多的限制:

  • 比如 Ruby 用{protected method}表示一种函数让其只能被自己和子类所访问
  • C++ 引入{friend function}来表示一个函数不是类的成员函数但是能访问类的私有成员

当一个概念越来越难以理解的时候,就到了一个改变的节点上。比如减少概念:Swift就没有protect关键字 why;但更有效的思路是引入更加简单函数式

函数式的函数

数学家,和程序员一样,对概念的 封装组合 有着巨大的需求。 例如英国数学家在一本著作中花了379页推导出1+1=2

他们是怎么管理好这么多封装,最终能串联起来解决问题的呢? 可否把他们的经验拿来借鉴?

答案就是 数学函数

数学函数输入集合到输出集合映射

而函数式语言的函数特性就是 数学函数

这和 面向对象 的函数可不一样,我们一般而言的 函数/方法 ,都是一些statement的集合。也就是说,他可以对外界环境进行改造 (比如网络请求,数据库请求),也可以完全没有输入和输出,更不用提说什么映射了。

重构1:加入纯函数

但其实要想借鉴过来也很简单,比如大多现代语言都引入了 闭包一等公民 ,在此基础上只需要给日常所写的函数加一个约束,就可以做到类似数学函数的效果。(加了这些约束的函数被称为 纯函数 ,或 引用透明 )这个约束就是:没有副作用(只能加工参数,输出结果,不能修改外界环境)

如果我们平时多写纯函数,问题就已经得到了一定的简化。

重构2:加入不可变特性 Immutable

纯函数 带来的一个好处就是 不可变 immutability ,因为 纯函数 只能处理输入,返回输出,那外界的状态就可以通过拷贝的方式传递进来,不用修改原来的值:

比如对于把x这个集合传入各种各样的函数内,x并不会改变。

这个好处不言而喻,不可变意味着线程安全,意味着不必因为一些bug去查看每一个可能产生的函数。

例如Swift就引入了这个特性,除了 class 都是 值类型 ,传递即拷贝,并通过 copy-on-write 实现按需拷贝,降低空间开销。

重构3:控制副作用

纯函数 不是一个褒义词,如同 副作用 不是一个贬义词一样。

有人肯花钱让我们做程序的原因就是编写程序通过写一些不纯函数来产生副作用(例如改变数据库里的数据)

用户不关心代码,只关心你能带给他们什么样的 副作用 (其实对于他们就是想要的作用) ,只有程序员才关心这个过程够不够优雅。

问题在于如何处理 副作用

答案是 ,换句话说就是一层封装,把真正要产生的 副作用 藏起来。调用 只能通过一个如何修改该值的函数给它,由这层封装来帮你实现 副作用

如果是状态修改,有一种方式是:当有冲突发生的时候,会重新执行需要再次更新的函数。(类似数据库的transaction)

如果是修改外部环境,一般会把请求放到队列里,统一派发。

即使是函数式语言, 副作用 也是要做的,不然这个程序就毫无意义,只是我们把 副作用纯函数 隔离了起来,让他们的影响在一个可控的范围内。

这种隐藏状态,通过传递函数修改状态的管理 副作用 的方式,就是我们下面要聊的状态管理思想。

总结,函数式带来的最大好处?

那就是并发!

共享全局状态是维护的噩梦,单纯的乱代码还不足以造成无法理解,可以通过清理最终理清,但如果是并发问题的代码,需要釜底抽薪的改造才可能把各种线程间的协作交接清楚。

即使我们不引入 状态管理框架 ,仅仅是让数组不可变,用Promise/RxSwift之类把异步的代码梳理好,也至少能让出现问题的线程拿到了不同版本的数据,由于数据间的隔离就不会有这边遍历那边修改导致的闪退。

如果在应用了Promise/RxSwift之外,引入了 状态管理框架 ,就意味着有了一个方便的途径,可以直接声明一种安全的共享状态,所有的修改都是统一控制的,不仅仅不会出错,还可以让每次修改都能有序的生效。

Source

关注下面的标签,发现更多相似文章
评论