GOTOCON : 函数式编程 made simple

面向墙外编程系列001:GOTOCON : 函数式编程 made simple程序员

👨‍💻 Conference : GOTOCON 2018数据库

​👤 Speaker : Russ Olsen[1]express

🔗 Original Link : GOTO 2018 • Functional Programming in 40 Minutes • Russ Olsen[2]编程

🏷 Tags : 函数式编程数组


函数式编程已经不是一个新概念了,最先能够溯源到上世纪五十年代的 Lambda calculus[3] 和 Lisp 。尽管如此,人们对函数式编程存在必定误解:学术性质太强,而且很是复杂。实际上并不是如此,你将会看到函数式编程的思想其实很是简单。安全

学习函数式编程须要丢弃全部你所知道的编程思想吗?这种说法部分正确,由于函数式编程思想的确很不同。可是这等因而把函数式编程过分神化了,函数式编程和面向对象同样,都只是一种为了下降代码复杂度的编程模式。面向对象很成功,可是代码依旧很复杂,函数式编程是一种彻底不一样的 approach,也许能作得更好。bash

先来梳理一下咱们熟知的构成程序的概念有哪些:微信

  • 变量、赋值、循环、控制流
  • 基本数据类型:string integer
  • 组合数据类型:arrays、hashes
  • namespace
  • classes、继承、方法
  • 最上层的,就是最终的 program。

难道咱们要把这些概念也丢弃吗?显然不能,因此,学习函数式编程不是从零开始,把它看做是对已有编程概念的 重构 更加恰当。数据结构

与其说学习函数式编程要 “Forget everything you know about programming”,不如说要 “Refactor everything you know about programming”。并发

一个应用程序的重构过程

假设你在开发下面这个程序。

最开始程序 works ,可是随着时间发展,需求变动,你须要添加新的模块、删除某些不须要了的模块,还须要在不一样模块之间修改依赖关系。 一段时间后,it works ,but in a mess。

如今坐下来,从一张白纸开始,从新重构应用:

可是有趣的是,当咱们要重构一个系统的时候,并不会真的从头开始,重写全部代码。咱们作的是,把以前 works 的组件抽出来,从新组织。

重构事后,你加了一个 message bus,来串联不一样组件之间的通讯。

构建函数式编程世界的蓝图

面向对象编程语言处处是下面这种的定义和限制:

During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Objectif the type parameter is unbounded.

Protected methods: a protected method is similar to a private method, with the addition that it can be called with, or without, an explicit receiver, but that receiver is alwaysself(it’s defining class)or an object that inherits fromself(ex: is_a?(self)).

A friend function of a class is defined outside that class’ scope but it has the right to access all private and protected members of the class. Even though the prototypes for friend functions appear in the class definition, friends are not member functions.

其种类繁多,你仔细想过没有,这是否是让问题自己过于复杂了?咱们须要记住这么多限制吗?也许咱们须要换一种方式、重构一下? 那就让咱们开始吧!

仍是从一张白纸开始:

把要保留的编程概念加进来:

该如何把底层的编程语言基础,和顶层的程序组织起来呢?让我慢慢来看。

第一把武器:纯函数

也许数学家们可以帮咱们的忙。数学和计算机科学在一个方面很像:抽象,试图从最基本的元素开始,抽象出上层概念。数学家写了一本叫《Principia Mathematica》的书,从很是很是简单的公理开始,推导出了整个数学大厦,光是证实 1 + 1 = 2 就花了 300 多页。。。

那么咱们能从数学家那里借来什么概念呢?没错,函数!

数学意义上的函数和咱们程序中的函数不同,函数就是一个集合到另外一个集合的映射:给我一个输入,我还你一个输出,不会对外界作任何操做。并且更重要的是,对于任何特定的输入,我还给你的输出必定是同样的。

而编程语言中函数的概念不太同样,编程语言中的函数能够删除文件、插入数据库,而这些属于反作用。并且,编程语言的函数给定一个输入,输出结果可能会不同,好比输出依赖于当前时间的状况。

若是咱们想借鉴数学中的函数这个概念,就须要给编程语言中的函数强加一些特定的规则,让他表现得和数学函数同样。(咱们把知足这种条件的函数叫作纯函数。)这些规则很简单:

  • 同样的输入,产生同样的输出。
  • 没有反作用。

咱们这样作,并非上层设计,通过严格的证实,认定纯函数必定能让咱们的代码更简单,这只是一种指望。 就像面向对象同样,咱们抽象出现实世界一一对应的类和对象,这并无严格的证实能够保证这样作就是对的,只是咱们指望这样作能够。

咱们须要问两个问题:

  • 纯函数有什么优点?
  • 若是纯函数有优点,如何在语言层面确保纯函数可以实现?

When asked, “What are the advantages of writing in a language without side effects?,” Simon Peyton Jones, co-creator of Haskell, replied, “You only have to reason about values and not about state. If you give a function the same input, it’ll give you the same output, every time. This has implications for reasoning, for compiling, for parallelism.” — From the book, Masterminds of Programming[4]

如前面所说,纯函数核心在于对于特定输入,永远有同样的输出,且没有反作用。有了纯函数,你就只须要考虑 values 了,不用管 state了(相信你也据说或经历过共享变量带来的痛苦)。 这使得函数式编程具有如下几个优点:

纯函数更容易 reason about

纯函数不会说谎,你能从函数定义获得几乎全部信息。 纯函数更容易组合

这和 unix 的 pipeline 很像。

val x = doThis(a).thenThis(b)
                 .andThenThis(c)
                 .doThisToo(d)
                 .andFinallyThis(e)
复制代码

纯函数更容易测试、debug

显而易见,纯函数只依赖于输入,不须要考虑其余复杂的状态。

纯函数更容易并行化

“If there is no data dependency between two pure expressions, then their order can be reversed, or they can be performed in parallel and they cannot interfere with one another (in other terms, the evaluation of any pure expression is thread-safe).”

只要两个纯函数之间没有数据依赖,他们的执行顺序就能够任意替换,或者能安全地并发执行,换句话说, pure expression 是线程安全的。《七周七并发模型》一书中也将函数式编程看成一种重要的并发模型,甚至这可能就是将来的趋势,由于这种方式最简单,你彻底不用考虑锁的问题。 想要了解纯函数更多的优点,推荐看这篇文章:The Benefits of Pure Functions | alvinalexander.com

咱们回答完了第一个问题,知道了纯函数是有优点的,如今咱们的蓝图上添加了第一把武器:纯函数。

第二把武器:持久性数据结构(Persistent Data Structure)

接下来回答另外一个问题:如何在语言层面确保纯函数可以实现?

咱们都知道,通常的数据结构是可变的,好比一个数组,没有任何限制阻止你修改其中的某个元素:

可是这样一来,就违背了咱们前面说的规则:不会产生反作用。

有一个解决方案:让全部的数据结构都不可变。一旦你建立了这个列表,你不能修改它;一旦你建立了这个 hashmap,你不能修改它。固然,函数确定是须要对输入作一系列操做(计算)的,不然这个函数有何意义。只不过操做的方式不同: 好比有一个 array :x = ["a","b","c"],在一个纯函数内要执行 x[1] = "Q",这会先将 x 复制一份,把复制品的第二个元素修改成 "Q",原来的 x 没有变化。(copy on modification)

如今咱们不用担忧调用的函数会在咱们不注意的状况下修改传入的值了,不过这会带来性能问题,由于拷贝太多了!所幸有人很聪明,想出了Persistent Data Structure,使得拷贝的次数尽量达到最低。 好比,一个数组,在底层多是一个树形结构。

要修改的时候,只会修改树的其中一部分,其余部分是和原来的元素共用的。Persistent Data Structure 的原理这里就不细讲了,有兴趣的能够自行了解。

到目前为止,咱们获得了第二把武器:Persistent Data Structure.

第三把武器:链接外部世界的桥梁

而后再来仔细想一想什么是 side effect。修改文件、插入数据库、调用外部服务,这些对于咱们程序员来讲属于 side effects。可是对于用户来讲,这就是他们所指望完成的操做!一个报表应用的使用者只关心数据库里面的数据、生成的报表,他们才不关心代码,也不懂什么函数式编程。若是一个应用彻底没有“反作用”,那这个应用也就没有存在的意义。

Side effects are what we paid to do !

因此咱们须要在“函数式编程理想世界” 和 “充满 side effects 的复杂多变的现实世界”之间架起一座桥梁。

其中一个现实世界的的问题:如何实现可变状态?好比一个网站的访问次数计数器,这就是一个可变状态。但是在纯粹的函数式编程世界里,数据都是不可变的,Clojure的解决方案,是 Atoms。能够把 Atoms 看做是包含可变状态的容器。

修改一个 Atoms 的方式是:丢给它一个函数,atoms 接收到这个函数,把 atoms 当前的 value 传递给函数,函数根据这个 value 计算产生新的 value,返回给 atoms,atoms再将此 value 做为新的 value 保存下来。

Atoms 的强大之处还体如今,当有两个函数同时到达时:

fx 和 gx 两个函数(假设都是加一函数)同时传递到 atoms 面前,他们看到的值都是 59,通过计算,两个函数产生的结果都是60。可是,老是会有一个函数(好比说是 fx )先把 59 更新为 60。另外一个函数(gx)再次把值更新为 60 的时候,atoms 就会检测到底层的数据已经发生了变化,而后让 gx 再执行一次!

因为没有 side effects,因此重试是没有任何问题的。这属于 atoms 的冲突检测机制。 可变状态的问题解决了,那么更新数据库、操做文件这些操做该怎么办呢?Clojure 有 Agents。

简单来讲,Agents的做用是接受带有 side effects(好比进行数据库操做)的函数,加入到一个队列里面,依次执行。 如今咱们有了第三把武器:链接外界的桥梁。

而这,也就是咱们设计的函数式编程的最终蓝图了。是否是很简单,并无想象的那么复杂。

总结

函数式编程并不是万能解药,它不能彻底消除重复代码,也不能阻止你写出垃圾代码。

函数式编程经过引入纯函数,具有了面向对象编程所没有的优点,能让代码更简单,更容易理解。而持久性数据结构使得纯函数变得可能。将来是一个多核时代,函数式编程语言天生线程安全(由于没有可变数据结构,一个线程不可能修改另外一个线程的数据),很是适合在多核环境运行。

函数式编程编写的函数没有反作用,可是应用的使命自己就是反作用,要链接 “理想的函数式编程世界” 和 “复杂的充满反作用的现实世界”,能够用 atoms 或者 agents 这类桥梁。

面向对象依旧是当前应用最普遍的编程思想,也构建了无数大型项目,而函数式编程相比更为简单,将来前景确定会愈来愈好。

参考资料

[1] Russ Olsen: russolsen.com/

[2] GOTO 2018 • Functional Programming in 40 Minutes • Russ Olsen: www.youtube.com/watch?v=0if…

[3] Lambda calculus: en.wikipedia.org/wiki/Lambda…

[4] Masterminds of Programming: amzn.to/2bedXb4

[5] The Benefits of Pure Functions | alvinalexander.com: alvinalexander.com/scala/fp-bo…

欢迎关注我的微信公众号:面向墙外编程。带你得到更多高质量的墙外编程资源。

相关文章
相关标签/搜索