写给前端工程师看的函数式编程对话(系列文章)

2021-03-14 12:52:44 +08:00
 FrankFang128

第 0 天

学生:方,我理解不了数据不可变。

方:正常,我在学 Haskell 之前也理解不了。

学生:你的意思是,我要真正理解数据不可变,就必须学 Haskell ?

方:也不一定非得是 Haskell,任何一门「支持函数式」且推崇「数据不可变」的编程语言都可以。

学生:为什么,用 JS 就理解不了吗?

方:可以这么说,至少我无法用 JS 来讲解函数式,可能是我水平不够。

学生:那么我学会 Haskell 就能理解数据不可变吗?

方:不是这样的。

学生:什么意思?

方:Haskell 能发挥「数据不可变」的优势,这样你才会觉得「数据不可变」是好东西。另外,数据不可变不需要理解,这只是一个约定。

学生:约定?

方:是的,你以前学的编程语言约定「数据可变」,并且向你展示了「数据可变」的优势。

学生:对啊,我觉得数据可变才是正常的。

方:其实不是,你只是先入为主而已。你没发现数据可变的短板而已。

学生:短板是什么?

方:你现在不会认为这些短板是短板,因为你还没有见过「数据不可变」的长处。

学生:我确实没见过它的好处。我觉得它多此一举。

方:这就是问题所在,你不会认同「无副作用」「引用透明」「纯函数」是优点,你目前认为这些概念是硬凑出来的概念,对不对。

学生:是……

方:你被目前的计算机教育给局限了。你知道面向对象、图灵机,但不知道邱奇数、lambda 演算、Y 组合子,但是他们是同样重要的。

学生:确实是第一次听说……

方:那么,你想学函数式吗?

学生:学完有什么好处?

方:没有,只是拓宽你的思维,你可能依然继续用 JS 或者 Java 编程,很少用到这些技术。那么,你还想学函数式吗?

学生:你要花多久讲完?

方:你学会并习惯指令式编程语言,比如 JS 或者 Java,用了多久?

学生:大半年。

方:那么你至少需要同样长的时间才能习惯函数式,而且习惯之后就回不去了。我可能讲一段话,你要花一周甚至更久才能理解。你还想学函数式吗?

学生:听起来弊大于利?

方:我保证利大于弊,只不过短期内看不到利而已,学完之后你对编程会有完全不同的认识,但工资不一定涨。

学生:好吧,那我试试看,想学一学。

方:我们明天开始第一课。

5246 次点击
所在节点    分享创造
34 条回复
FrankFang128
2021-03-14 13:30:20 +08:00
方:我跟你举个例子,你应该知道 ++i 比 i++ 的性能要好很多吧?

学生:哦?为什么?

方:原因不重要,你可以看看[这篇文章]( https://www.omegaxyz.com/2017/05/20/aboutippandppi/),也可以不看。假设你已经知道 ++i 比 i++ 性能要好很多,请问,你在写 for 循环时,写 i++ 还是写 ++i 。

学生:我以前一直写 i++,听你这么一说,我是不是应该写 ++i

方:不用,因为编译器帮你优化了,编译器一旦发现 for 循环的第三个语句是 i++,会自动优化为 ++i,所以不需要程序员自己记住这么多规则,网上这种乱七八糟的优化技巧太多了,你能记住几个

学生:编译器这么智能!

方:你也不想想,写编译器的那帮人比写 JS 的人智商高多少。

学生:也是

方:甚至有的时候,你为了提高性能而采用的怪异写法,会降低程序的性能,因为它得不到编译器的优化。编译器一般会优化常见的写法。

学生:原来是这样

方:所以程序员应该尽量写符合社区习惯的代码,只在性能瓶颈处手动优化性能。但是由于 JS 和 Java 这帮人常年的「教育」,像你这样的新人已经都认为应该尽量不用「递归」,所以 JS 和 Java 的编译器没有必要花时间优化小众写法,优化之后反而会造成安全和 debug 相关的问题。

学生:那说递归「开销大」和「浪费内存」是不是也是类似的误区。

方:是的,如果某个语言特性被程序员用得多,编译器就会想尽办法让它变快。现在,你可以放弃你对递归的偏见了吗?

学生:可以,那我是不是还得放弃 JS 和 Java 的编译器?

方:是的,这两门语言的社区文化与函数式不太兼容,而且主流版本的编译器是不支持这些优化的,也许未来的版本有。

学生:那我再多嘴问一句,「递归」没有缺点吗?

方:也有,以后会讲,但是瑕不掩瑜,利大于弊。

学生:好,我先记下来,虽然还没有完全被说服。

方:我们是从哪聊到这的?哦,是从「你没法第一时间给出递归的写法」聊到这的。那么我给你出第二题,写一个将字符串反转的函数,不能违反「数据不可变」的约定哦。

学生:递归写法我会



reverse = (string) => {
if(string.length <= 1){return string}
let last = string[string.length-1]
let head = string.substr(0, string.length - 1)
return last + reverse(head) // 把最后一个字符移到前面,然后递归
}



方:写得还挺快

学生:丢下偏见之后果然写得快很多,不过还是觉得效率会慢、内存会浪费

方:过几天你就习惯了,我也会教你怎么优化。再来一个,快速排序

学生:简单



/*1*/ quickSort = (array) => {
/*2*/ if(array.length <= 1) {return array}
/*3*/ let [pivot, ...rest] = array
/*4*/ let small = rest.filter(i => i<=pivot)
/*5*/ let big = rest.filter(i => i>pivot)
/*6*/ return [...quickSort(small), pivot, ...quickSort(big) ]
/*7*/ }



学生:这个快排写得是真的爽,但我还是担心浪费内存,第 3 、4 、5 、6 行全是内存复制

方:哼,那是因为 JS 和 Java 的数据可变,所以不能直接复用数据啊。如果数据是不可变的,`let [pivot, ...rest] = array` 可以直接复用内存啊,rest 和 array 复用同一片内存问题不大

学生:好像是这么回事,那 rest.filter(i => i<=pivot) 呢

方:这个内存很难优化。但是你要知道,你用函数式写只用 5 行代码,用指令式写可能要 20 行代码,函数式追求的是逻辑上的简洁,先让逻辑变美,然后等遇到性能瓶颈了再上优化

学生:好吧

方:等下,你怎么好像对「数据不可变」还是没有完全接受啊,总想着改原来的内存?

学生:才第一天嘛,过几天我才能适应

方:好吧,今天就先到这里,明天继续。
FrankFang128
2021-03-14 13:33:01 +08:00
大家请忽略 1 楼,1 楼内容是我发错了。
FrankFang128
2021-03-14 18:58:15 +08:00
@Livid 能把一楼删掉吗?
cmdOptionKana
2021-03-14 20:05:50 +08:00
干货啊
GeruzoniAnsasu
2021-03-14 20:38:13 +08:00
我以为 “写给前端” 会从 “你怎么让元素 B 的颜色随着按钮 A 的位置移动而改变” 这种角度入门

之前有一个帖子问 怎么实现对象值之间的相互约束,比如定义 C=A+B,改变 AB 的值能直接导致 C 结果改变。这种具体场景才好解释函数式的特点和必要性


写给前端为啥不直接讲讲怎样 “make something reactive” ?
mmtromsb456
2021-03-14 20:44:31 +08:00
希望楼主做个汇总版放 blog 之类的地方,支持关注一下
AEDaydreamer
2021-03-14 22:16:29 +08:00
我最近正好在看 functional programming in JavaScript 相关的内容 ,受益良多
lbyo
2021-03-15 09:54:27 +08:00
++i 和 i++ 是有区别的吧
yunyuyuan
2021-03-15 10:18:09 +08:00
jinliming2
2021-03-16 01:22:11 +08:00
JS ES6 提案有尾调优化,但目前好像只有用 Webkit 的 Safari 支持了,其他的 Chrome 和 Firefox 都还没实现。
FrankFang128
2021-03-16 03:22:18 +08:00
@jinliming2 后来取消了这个提案
namelosw
2021-03-16 09:33:43 +08:00
@jinliming2 一开始是 Safari 和 Chrome 实现了, 然后开会的时候 Mozilla 说我们还没做, Edge 做不了不做了.

最后就进行不下去了…

然后 Chrome 一看算了, 反正别人也不做, 就把自己的 TCO 也删了, 现在好像只有 Safari 的还留着.
abersheeran
2021-03-17 09:51:10 +08:00
你这个“编译器应当优化尾递归”的说法放在 Py 里可能可以,但是放在 JS 里绝不可能的。JS 的历史包袱可是最重的。
huabalance
2021-03-17 10:59:33 +08:00
醍醐灌顶,期待更新。
FrankFang128
2021-03-18 11:11:18 +08:00
@huabalance 已更新
FrankFang128
2021-03-18 12:26:09 +08:00
换个地方连载 https://v2ex.com/t/762762
sillydaddy
2021-03-19 17:04:49 +08:00
“递”“归”解释的很形象哦
cyrbuzz
2021-03-20 22:23:09 +08:00
递归最大的缺点是不是性能不高?
Wincer
2021-03-20 22:45:23 +08:00
不错,有种让我觉得在读《冒号课堂》的感觉。
Chingim
2021-03-20 22:53:05 +08:00
写得挺好的,关注了(一般我不夸人

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

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

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

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

© 2021 V2EX