阅读本文请自备纸、笔、头痛药。
序
fold left
和fold right
是函数式编程里表达遍历求值的其中一种重要表达式。在此不过多介绍,本文假设读者熟悉并能熟练使用fold left
与fold right
进行遍历求值。
问题来源于Scala小红书里一个小习题。大致意思是,如何利用fold left
来实现fold right
。一般人,包括我,第一直觉就是反转list,然后flip一下二元函数。
但是原文中还说了一句:
用fold left来实现fold right很有用,因为可以用尾递归的方式实现,意味着无论列表有多长都不会产生栈溢出。
那用reverse显然是不符合题意的,因为会导致栈溢出。好奇心驱使着我去寻找这个问题的正确解答,果不其然,我又被惊艳到了。
解
|
|
如果能一下子看懂并理解,敬你是个天才。一开始看不懂、被绕晕也是正常的。欢迎参考我接下来的解析。
形状
fold left
foldl f z [1..10]
的求值形状如下:
fold right
foldr f z [1..10]
的求值形状如下;
上述用foldl
实现foldr
本质上就是利用foldl
生成foldr
那样的一个求值形状(闭包),然后apply到初始值z上,求值那一长串得到最后结果,而用foldr
实现foldl
亦同理。
以foldl
实现foldr
为例。
以上应该能够为你建立起一些intuition了,不过对于我来说其实还不够,更重要的是建立起对fold
的求值结果是一个函数的直观感受。
重新审视
fold right to fold left
现在重新看foldl
的正常递归实现:
换一种等价写法(关键)
再进一步
假设我们有这样一个cons
实现:
cons
用foldr
实现:
经过对比,g
一样可以用foldr
实现:
因此,将g
代入foldl f z xs = g xs z
,得到: