你们认为函数式编程语言未来可期吗?

2020-01-09 13:22:12 +08:00
 FaiChou

这几天有三个关于语言类讨论的帖子:

看了讨论内容或多或少都有些提到了 FP 的概念, 貌似 FP 概念在大众已经流行起来了?

结合今年我们老板发的一篇为什么函数式编程没有流行?, 以及今年我们公司全员学习和使用 Clojure, 博客最后有一段:

未来是 FP 的,现在已经是大概率事件。

OO -> 混合 -> FP

FP 这个词注定会被用烂,这是另外一个故事了。

想听一下大家对 FP 语言的看法, 未来发展如何, 以及你们平时使用过的 FP 语言有哪些? 对比 OO 有哪些优势劣势? 你们有在业务项目中使用 FP 语言吗?

(这不是一个关于某种语言讨论的帖子, 不想引起战争)

8533 次点击
所在节点    程序员
47 条回复
fanfou
2020-01-10 12:12:45 +08:00
FP 确实心智负担会有点大
no1xsyzy
2020-01-10 13:42:46 +08:00
纯函数没意义,coq agda 没人用啊
因为纯函数根本不存在 “效果”

面向对象是灾难,直接上翻译版 https://linux.cn/article-11519-1.html
竟然免不得和 #39 和 #40 链的两篇文章一致……

另外,写 FP ( Racket 弱纯),没动笔时会觉得 “好难啊这怎么做”。
结果开始动笔就很流畅地写完了。
鬼知道我经历了什么。
说 FP 考验智商的,是没写过 FP 的。
secondwtq
2020-01-10 21:25:02 +08:00
居然顶到了某种字数上限,分两段发 ...

首先我无法理解楼主为什么会有在这个社区发这个帖子的想法 ...
虽然是 F2EX,但是我并不期待这里的人对所谓 FP 能有什么高见,至少在这里的人对 FP 有个哪怕是最基础的认识之前
说实话,哪怕是在 HN 上,这种帖子也是引战贴(并且随便一搜就能在同一个站点找出十个战场来),还是去 LtU 抱团取暖吧 ...
关于“FP 概念在大众已经流行起来了”,知乎上有相关问题: https://www.zhihu.com/question/30190384 为什么这两年函数式编程又火起来了?

楼主要想讨论“函数式编程语言的未来”,首先要解决“函数式编程是什么”的问题。这在任何一个主题和函数式编程无直接关系的社区中都是一个无法解决的问题。就连 JavaScript 和 Ruby 这种社区中也免不了三天两头就有人出来科普什么“函数式思维”,说明就算这些一只脚在 FP 里面的语言也没法解决这种问题 ...

我水平有限,不敢为“函数式编程”这个词给出一个足够“函数式”的定义(“函数式编程”这个词背后的 implication 太多了)。但是在我这几年折腾函数式编程的过程中,有那么两点最大的感受:

第一是对于任何 non-trivial 的程序而言,最重要的东西之一是管理复杂度( Complexity )。管理复杂度最重要的手段是模块化,或者按照 FP 的黑话,叫组合( Composition/Composable/Composability )。而这也是 FP 最看重的东西之一。

Composition 是什么意思?就是看上去非常牛逼如同魔法的东西,一点点拆开发现是由非常简单的东西构成的。比如乐高方块组合成完整的乐高玩具,细胞组成器官,器官组成人体,晶体管组合成逻辑门再组合成芯片,if、for 等操作组合成函数,函数再组合成程序,若干个程序模块组合成一个完整的软件系统。比如说人体一定程度上是 composable 的,因为器官可以移植,器官移植需要配型,所以器官又不那么 composable。
在编程语言中的例子比如:C 的宏有不 Composable 的方面(比如使用有副作用的片段展开可能会导致副作用执行多次); goto 不如 if 和 for 更 composable:用 if 和 for 写的程序可以随便拷贝到其他地方,作用还是一样的(在预期之中),但是纯 goto 写的做不到; Checked Exception 的问题也是出在不 Composable 上——使用其他模块除了需要了解模块本身接口之外,还需要折腾模块本身,以及可能的底层模块暴露出的异常;多重继承会出现 Diamond Problem,这意味着在使用多重继承时必须挨个确定每个基类都有哪些基类,也不 Composable。Implicit Coercion 看上去允许不同类型的值之间 compose,但是往往也会伤害 composability:比如 C 中无符号和有符号值之间的比较。锁也是不 Composable 的(见 https://en.wikipedia.org/wiki/Lock_(computer_science)#Lack_of_composability )。JS 中的 callback 相比 Promise 的缺点也是不 Composable:值是编程语言中最 Composable 的东西,并且 Promise 的错误处理也更有利于 compose。

可以发现不 Composable 的地方往往体现为各种各样的“坑”,而 Composable 的地方我们一般都 习以为常。

对于管理复杂度这一问题(比如设计软件),Composition 提供了极其明显的好处:在理想情况下,系统可以被分解为若干模块,模块可以递归地分解为更小的模块。特定的开发者在特定的时刻只需要关心自己正在开发的模块,以及所依赖的模块的接口。模块可以独立地开发与测试,模块之间的依赖被最小化,实现细节和复杂性被隐藏在抽象接口之后。

较差的 Composability 对于简单程序来讲问题不明显,项目越大,造成的麻烦就越大。

函数式编程提供了实现 Composability,管理复杂度的理论。函数式编程提倡通过小函数组合成更大的函数的方式来构建软件,对于 Composability 极为重视(某些 FP 厨经常扯的猫论更是从根上就是关于 Composability 的理论)。

函数式编程同时认为 Side Effect 是 Composability 的最大障碍,虽然“函数式”这个词可以被套在很多东西上,但是 Side Effect 不是其中之一。如果以 Side Effectful 的方式写程序,就意味着当使用某一模块时,必须考虑这个模块是怎么实现的,可能触发哪些 Side Effect,这个模块依赖的模块又可能触发哪些 Side Effect,这些被依赖的模块依赖的模块又可能触发哪些 Side Effect ...

但函数式编程并非完全拒绝 Side Effect (这不可行),而是强调应该 管理 Side Effect。比如 Haskell 强制使用类型的方式标记哪些函数有 Side Effect,哪些函数没有,有 Side Effect 的函数可能会包含哪些 Side Effect。这就强制你把有 Side Effect 和没有 Side Effect 的函数分开,并且对无 Side Effect 的函数实现了 Composable 的目标。
ML、LISP 和 Scala 等则对程序员比较信任,除了变量赋值稍微麻烦一点,使用 Side Effect 和 Imperative 语言没有什么不同。但从语言设计到库再到代码风格都偏向于限制 Side Effect 的范围。

---

第二是对于简洁、优雅的理论框架的追求。(注意这里的“优雅”不是语法上的“优雅”)

什么叫做“简洁、优雅的理论框架”呢?我举一个和目前 V 红话题相关,F2EX 应该能懂的例子:
https://raphlinus.github.io/ui/druid/2019/11/22/reactive-ui.html 这是我前段时间研究 GUI 时找到的一篇文章。文章试图用一个“简洁、优雅的理论框架”,统一目前流行的 React、Flutter 等 GUI 框架的思想——注意意思不是统一这些框架,而是尝试找出这些框架实现思想的共同点,并总结起来。注意文章作者自己就是 Berkeley 的 PhD,同时自己也在开发一个 GUI 框架。
该文章提出:
* 所有的 “reactive” 框架(该文章并未明确定义 “reactive” 这个词,只是用它来概括现在流行的 UI 框架)都可以被视为一个 Tree Transformation 的 Pipeline ——从状态的 tree,生成高层的组件 tree,再生成底层的元素 tree,布局、渲染后变成屏幕上的像素。
* Tree 有两种表示形式,一种是 linked data structure (就像数据结构课本上教的二叉树),另一种是 “trace of execution”,即遍历 tree 得到的 flatten 表示。
* 不同的 GUI 框架在实现这个 pipeline 的很多细节有不同。其中比较重要的部分之一是当输入(状态 tree )变化之后,如何高效更新 pipeline 中的其他 tree——也就是 React 中的 Reconciliation,Angular 的 Dirty Checking 之类的。
这个“理论”最牛逼的地方在于它不仅统一了 React 和 Flutter 之类所谓“声明式”( whatever it’s called ...)框架,还通过不同的“Tree 表示形式”,把渲染中间层和 imgui 这种 Immediate 模式也囊括进来,因此说是“Unified Theory”。这个文章是所有关于 GUI 的资料中,对我最有帮助的。
另外作为被王垠在去年 12 月 24 日喷过的人,我发现这套理论与现代优化编译器的设计思想有非常相似的地方。另一个 GUI 框架的开发者在看这篇文章时也有同样的感受: https://blog.anp.lol/rust/moxie-intro,并且他还找出了其他领域的例子。这时候直接搞到我颅内高潮了 ...

其他相关的例子包括:
* 天文学在牛顿之前一直在试图解释天体的运行规律。
* 后来的物理学两百年来一直在追求把四种基本作用统一的理论。
* 生物学使用演化论解释不同生物之间不同特征的关系。
* 图形学使用 Path Tracing 渲染所有光学效果。
* 大数据处理基于 MapReduce 模型操作数据(嘛这其实是 FP 的东西 ...)。
* 编译优化领域使用 Polyhedral Model 建模程序的行为。(编译优化好像还不存在所谓“Unified Theory” ... 这是让我感觉很别扭的一点)

UNIX 的“一切皆文件”,“一切皆文本”和某些 OOP 语言中的“一切皆对象”其实同样也是非常熟悉的例子。

与之相对的是,很多编程语言的基础是很随意的:C 最开始是为了折腾 UNIX 整出来的,C++ 则是在 C 的基础上的另一个个人项目。JavaScript 是为了跟 Java 的风十天写出来的。PHP 是为了维护个人网站写出来的。Python 的性质也类似。
Java 反倒是最根正苗红的一个——Java 有 Guy Steele ( Scheme 设计者之一)的参与,泛型等则更有 Martin Odersky ( Scala 之父)和 Philip Wadler ( Haskell 设计者之一,同时也在所谓 “Monad” 概念的应用中有重要贡献)的参与。Pascal 同理。

基础“随意”是什么意思?意思就是语言的设计只是为了满足设计者当下的需求,缺少理论的指导。当然一门语言的设计一定有“为了满足某种需求”的目的,但是“我想做个语言来写博客”和“我们实验室想开发个语言来写 Theorem Prover”显然是两种目标。而这些“随意”的语言的设计者,在设计语言的时候,往往对“他眼前所要解决的问题”比对“程序语言设计”这件事要在行的多,换句话说“随意”的语言是由 PL 外行人做出来的 ... C 的成功是 UNIX 的成功,C++ 的成功则是建立在 C 的成功的基础上,PHP 和 JavaScript 的成功是 Web 的成功——它们都不是“语言本身”的成功,而是傍着别人的大腿获得成功的。

理论的指导能带来什么好处?
相对论预言了黑洞,量子力学在半导体上有很多应用,真的有了物理上的统一理论,指不定又会捣鼓出什么东西。
写算法,了解了贪心、动态规划、分治等套路,能帮你更好地理解与设计算法(虽然有很多题还是做不出来 ... 但是如果你不知道这些套路,只会更难)。

在编程语言中,循环可以被看做是递归的一种特例,那么可以把循环拿掉,用递归(以及 PTC )来实现循环。
Exception、Coroutine、Generator、Async/Await 等则可以统一在 Continuation 的概念下,那就只需要实现 Composable 的 First-class Continuation 就行了。
HM 类型系统则可以进行完整的 Type Inference,允许以动态语言的方式写静态语言——整个程序不需要写一个类型,却依然是类型安全的。

函数式语言则会如此设计:首先把 Lambda Calculus 拿来,Lambda Calculus 允许用非常简单的模型,表达足够的抽象。然后使用 Type 来扩展(限制)它,变成 STLC,再加入一些简单的扩展,比如 Parametric Polymorphism,Algebraic Data Type。最后把 int、bool 等 primitive type 放进去。基本上就是一个能用的语言。
整个语言核心就是这个样子,找不到多余的东西。每一个新的 feature 都是对之前语言非常自然的扩展。
再加入 Reference 用来处理 Side Effect,Modules 来组织程序,做个 Type Inference,这就是一个 ML 语言。
最重要的是,所有的东西都是 Composable 的。

当然 ML 自己有不那么 Composable 的东西,比如只支持 Rank-1 Type,可以说是为了 Type Inference 做的妥协。那也可以扩展到 Higher-Rank 和 Impredicative 到完全的 System F (只是这时候理论会告诉你 Type Inference 不好做了)。所有这些并没有限制程序员的能力,而是提供了更好的 Composability。
Side Effect 也不利于 Composability,这方面也有若干方法来管理,同样是为了更好地 compose。

所以说我对一个”函数式语言“的判定最重要的是它有没有建立在函数式理论的基础上。而至于什么乱七八糟的”First-Class Function“”Algebraic Data Type“和”Immutability“之类的具体的特性并不是最关键的,因为它们都是在这个理论上自然的延伸。

另外,我个人并不喜欢 FP 标榜“利于并发”来搞宣传工作,“利于并发”只是 FP 所提供的 Composability 的一个 Side Effect,而 Composability 本身是 FP 理论的一个 Side Effect。冲着“利于并发”折腾 FP 的,大概率会 totally miss the point。
secondwtq
2020-01-10 21:25:14 +08:00
几个 myth:
* 函数式编程需要"高智商"么?

我认为技术社区对函数式编程的陌生感是被建构出来的。或者说进入了”目前 imperative 占了主流,所以大家从一开始就接受的是 imperative 的话语体系,所以会对 functional 有陌生感“的循环。并且这个循环还存在”functional 的资料更少,生态更差“的负反馈。

函数式编程本身做的就是用非常简单的东西 compose 为更加复杂的东西,函数式编程的理论、大多数函数式语言和函数式程序走的都是这个路子,函数式编程本身并不比拼乐高难。

为什么说函数式编程是自然的,符合直觉的?为什么说函数式编程能够 Compose ?
举个例子:为什么要有 closure 和 first-class function 呢?
Closure 反映的是 Lexical Scoping,就是我在外面定义的一个东西,在里面一定能用(无论是一个 if 块里面,还是 for 块里面,还是函数里面)。这本来就是一个编程语言应该具有的属性。
first-class function 则是对编程语言中“值”的语义的自然推广——如果一个 int 可以来回传递,可以被存储,如果一个“对象”( OOP 意义上)可以来回传递,可以进行各种操作,那么为什么函数就不可以呢?
所以并不是“我要 FP,所以我要 closure 和 first-class function”,而是“我要一个正常的,不坑的语言,如果这个语言支持 lexical scoping 和 function,那么它就也应该支持 closure 和 first-class function”,closure 和 first-class function 根本就不是“语言特性”,而是正常的直觉和逻辑的结果。
只是因为很多语言有这两个限制,所以如果你用有此限制的语言入门,你就会先入为主的认为编程语言“就该”是这样的。有一天这个语言更新了,加了这两个“特性”,然后网上一堆 blog 就沸腾了——尽管这不过只是你应有的自由而已。

在 VFX 领域,软件市场大多被大厂商的专业软件所垄断,大多数用户也没什么编程能力。然而就是这么一个行业,最近几年刮起了一股”Node“的歪风邪气——不是 Node.js 的那个 Node,而是”节点“意义的 Node。
这是 Maya 中的 Node:
这是 Maya 新加的 Bifrost 特效系统: https://area.autodesk.com/dynamic_resources/area_blog_post_content/3129/html_content/11564262275.jpg
这是 C4D 中的 Node: http://www.llgmotiongraphics.com/wp-content/uploads/2012/09/Pin-Ball-Dynamics.jpg
C4D R20: https://www.maxon.net/fileadmin/_processed_/0/f/csm_nodes_preview_c2598d29d5.png
Softimage ICE: https://4.bp.blogspot.com/-bkZyxX3FmdY/UXdE4Mlii3I/AAAAAAAAoHY/EHFXSKgxsFk/s1600/Softimage+ICE+and+Maya+Fluids+collaboration.jpg
MODO: http://esteldan-3d-graphics.de/contents/Projects_Pics/Car_Rig_3.jpg
这是 Nuke 中的 Node: https://s3.amazonaws.com/pbblogassets/uploads/2013/02/Nuke-Nodes.png
这是 Substance 中的 Node: https://cdna.artstation.com/p/assets/images/images/009/213/162/large/lino-thomas-noodles.jpg
这是 MARI 中的 Node: https://learn.foundry.com/mari/Content/Resources/images/user_guide/node_graph_intro2_680x394.png
这是 Blender 中的 Node: https://pbs.twimg.com/media/ENFE9MyWsAAlAo6?format=jpg&name=large
这是 Clarisse 中的 Node: https://www.isotropix.com/var/img/cms/41/1463651663-lookdev-unified-shading-network-full.jpg
这是 Houdini 中的 Node: https://cpb-eu-w2.wpmucdn.com/coursework.lincoln.ac.uk/dist/7/135/files/2018/10/LTCAO_SS_11.jpg
最有趣的例子是 3ds Max,论技术力,3ds Max 的用户可能比上面的要偏低一点,自动麻将桌在 2011 版本加入了 Node-based Material Editor: https://cdnb.artstation.com/p/assets/images/images/016/919/059/large/paulo-lima-slatematerialeditor.jpg?1553972675,然后又在 2016 加入了 MCG: https://area.autodesk.com/blogs/the-3ds-max-blog/introducing-max-creation-graphs MCG 作者有 FP 背景,直接摊开了说这就是基于 .NET 的“Functional dataflow visual programming language”。
这年头软件不做个 node network,还真不好意思拿出去卖。
(就连 Unity 都在整什么 Shader Graph,Visual Effect Graph )
你可以去搜,VFX 工资并不比程序员高,一搜都是劝转 CS 的。然而这就是人家干活的东西。还是靠简单的东西 Composite 成复杂的东西,只有“表达式”和“节点”,没有“变量”。

最极端的例子是 Excel,在上面的”Monad 之父插手 Java“之后,还有个八卦是”Haskell 之父水过一篇关于 Excel 和 FP 的关系的 paper“( https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/excel.pdf?from=https%3A%2F%2Fresearch.microsoft.com%2F%7Esimonpj%2FPapers%2Fexcel%2Fexcel.pdf ),其中写到:Indeed, one of the great merits of spreadsheets is that users need not think of themselves as doing “programming”, let alone functional programming — rather, they simply “write formulae” or “build a model”.
MSFT 如果一开始就告诉 Excel 用户他们在”编程“,那估计没人会用 Excel——太多的神秘感都是被建构出来的。不过我倒是相信,如果告诉 Excel 用户他们在做”函数式编程“,可能要比本站的某些用户更好接受——毕竟”编程“这个东西一般大学才学,更别说什么”imperative“了,而”函数“初中就学了。

汉语,英语,德语,阿拉伯语,哪个更难?我作为汉语的 native speaker,在学到大量屈折的语言时,在看到阿拉伯文字时,感受和学习函数式语言是一样的。这也没碍着全世界人讲不同的语言。
(顺便,自然语言作为长期外行设计出来的东西,有很多不 composable 的地方。最简单的就是英语中的大量的不规则动词)
不过我觉得最要命的是,自然语言中每个语言会使用固定的语音集合,不同语言使用的语音集合是不一样的,而在长大之后我就感觉彻底失去了学习新的语音的能力 ...

* Java 一直统治世界,函数式编程就没有未来么?

虽然说完全的函数式编程暂且不太讨好,但是这个问题好像忽视了关于函数式编程的特性一直在不断地进入新的语言和旧有语言的新版本这一事实 ...

用 Bartosz Milewski 的话说:Surely you can’t help but notice that there’s been a steady stream of new functional features INVADING imperative languages. Even Java, the bastion of object oriented ...(注意用词 ...)

Guy Steele 更是直接说"We were not out to win over the Lisp programmers; we were after the C++ programmers. We managed to drag a lot of them about halfway to Lisp."

最后,就算一个语言里一点”函数式“特性都没有,什么 Global Variables are Evil, High cohesion Low coupling, Don't Repeat Yourself, Separation of Concerns 这些设计思想不也是通用的么?见得越多,你就会越重视 Composability 的作用,为了实现 Composability,你会自然地发现有时候有 Side Effect 不如没有,你会自然地认识到理论框架的重要性,这个过程并不需要给你强制灌输一些什么乱七八糟的“教义”。

---

我觉得这几个帖子应该引发的思考是:我们真的有过“在意”这件事情么?再看一遍:C 的成功是 UNIX 的成功,C++ 的成功则是建立在 C 的成功的基础上,PHP 和 JavaScript 的成功是 Web 的成功,Java 的成功是 Sun 和 C++ 的成功——它们都不是“语言本身“的成功。
什么是“语言本身”?考虑这样一个问题,当被问到“XX 语言为什么好?”时,你会怎样回答?
它的 IDE 支持好?它的库多?某个平台甚至某个行业钦定了用它?它的工作多(或者好招人)?它的语法“优雅”?或者单纯它的爹比较厉害?
这些都不是“语言本身”的东西。虽然这些东西会影响大家对其的评价(甚至决定你有没有听说过它)。
但是最有趣的事情是,这些东西不仅会影响对语言的评价,还会影响对“语言本身”的评价,能让人把坏的吹成好的,把好的贬成坏的,所谓 dssq。

说来不怕笑话,Haskell 有个(非官方的?) motto 叫“Avoid success at all costs”。这句话 ... 有点歧义,即“((Avoid success) (at all costs))” 和 “((Avoid) ((success) (at all costs))” 两种解释。但是共同的意思是,Haskell 社区早就看出了“Success”和“语言本身”没有什么关系,Haskell 这个“语言本身”不会向其他因素妥协,好的东西就是好的东西,Haskell 不会因为“Success”的需求而拒绝好的东西,也不会因为“Success”的需求而包容坏的东西。
如果有一天 Haskell 真的“Success”了,那么 Haskell 希望它给人的印象是安全、强大、一致、简洁与高效,而不是“xx 在用 Haskell 修福报”“大家都在用 Haskell”。
虽然说我并不认为 Haskell 真的做到了这一点,也并不认为 Haskell 真的能 “Success”。但是对我个人而言,在这一个网络被封锁和监视,数据被泄露与复制,一言一行一举一动都被记录的时代,能遇到这样一个不忘初心,牢记使命,“pure”的东西,是我的幸运(虽然它有点懒)。

类似的问题不只出现在编程语言领域。“Worse is Better”和“The Right Thing”光在计算机领域就对立了好几十年了。
编程语言天天讲什么 first-class,但是实际上“编程语言”这个东西在它的大多数用户心中什么都不是,连 second-class 都不算。
FaiChou
2020-01-10 22:42:49 +08:00
@secondwtq 非常感谢, 这个帖子只贴了我们老大的文章结论, 没有把相关论点贴过来, 可能影响讨论. 再者我现在也只是一名语言的学习者, 对语言的发展史了解不是很多, 你所讨论的这些对我有些超前, 一时半会消化不尽, 希望在 2020 年能够丰富我的阅读.
FaiChou
2020-01-10 22:50:59 +08:00
@secondwtq 想起来了, 你是这位大佬 https://www.v2ex.com/t/598363?p=1#r_7871265 . 膜拜.
no1xsyzy
2020-01-11 17:40:14 +08:00
@secondwtq 我崩溃了
花了好长时间去脑内构建的语言,其实就是 Node 方式表示 control flow 和 data flow,类似流程图式的但同样可以做到 reactive…… 还包括多重 continuation (实际上 if 的实现就是模式匹配成功的 continuation 和失败抛出的 continuation )以及主动执行 continuation ( event emitter )
结果 first-class continuation 一说就全囊括了,一方面我花这么长时间绕了大半圈想出这种设计感觉浪费了(虽然反正是放松大脑用的),一方面不用我自己取名了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/636465

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX