我正在学习Scala和功能编程世界的新人。我发现大多数方法,例如foldRight
,map
,filter
,reduce
...总是可以由foldLeft
编写(由foldLeft书写foldRight)用于使用尾递归)。
例如:
def map[A, B](l: List[A])(f: A => B): List[B] =
foldRight(l, Nil: List[B])((x, xs) => Cons(f(x), xs))
def reverse[A](l: List[A]): List[A] =
foldLeft(l, l)((xs, x) => Cons(x, xs))
def foldRight[A, B](l: List[A], z: B)(f: (A, B) => B): B =
foldLeft(reverse(l), z)((xs, x) => f(x, xs))
在我进入函数式编程世界之前,我认为map
和reduce
是函数式编程的两种基本方法。从这两种方法中,我可以构建更复杂的方法。但基于上面的例子,我认为foldLeft
应该是所有功能方法的基础,例如map,filter,...
在函数式编程世界中是这样吗?
由于
答案 0 :(得分:2)
一般来说,不,foldLeft
不是"父亲"所有其他方法,因为除了List
之外还有许多集合。例如,您无法通过Stream.map
实施Stream.foldLeft
,因为foldLeft
执行完整遍历,但map
不应该遍历流,因为它的懒惰(它可能是无限的)
至于List
,你大多是对的。但是,如果通过foldLeft
实现,某些方法效率极低。考虑list.drop(1)
。它应该只返回列表的尾部,但是foldLeft
会进行完整的遍历。
答案 1 :(得分:2)
我要将我的评论扩展为答案。
让我们从名为 Bunch [T] 的类型开始,它是 Traversable [T] ,是所有Scala集合的基本特征。它实现了所有常见的monadic东西,如 map [U](f:T => U)等。
现在让我们开始根据他们的签名对所有方法进行分类。在下文中, U 是 T 的超类型:
这些方法中的每一种都与其他方法有根本的不同。某些(例如聚合)需要对集合进行完整遍历(例如,在 Stream 上无法实现)。其他人没有。
我们可以为每个方法类定义一个“父”方法。例如,我称之为“副作用”的类I的父方法显然是 foreach 。我认为将 foldLeft 称为“聚合”方法的“父亲”可能是合理的。但我认为,总的来说,我们一定会遇到这种方法的问题。您不能总是根据另一个方法定义一个方法。 map 本身可以根据其他方法定义,但它需要两个: flatMap 和“unit”,一个构造函数方法,因此不在我的列表中。
答案 2 :(得分:1)
从某种意义上说,你是对的。在他1999年的论文A tutorial on the expressiveness and universality of fold中,Graham Hutton回顾了如何使用fold
来证明东西而不使用递归和定义递归函数,以显示这个函数的表达方式。