为什么使用foldLeft而不是程序版本?
因此,在阅读这个问题时,有人指出,而不是程序代码:
def expand(exp: String, replacements: Traversable[(String, String)]): String = {
var result = exp
for ((oldS, newS) <- replacements)
result = result.replace(oldS, newS)
result
}
您可以编写以下功能代码:
def expand(exp: String, replacements: Traversable[(String, String)]): String = {
replacements.foldLeft(exp){
case (result, (oldS, newS)) => result.replace(oldS, newS)
}
}
我几乎肯定会编写第一个版本,因为熟悉过程或函数风格的程序员可以轻松阅读和理解它,而只有熟悉函数风格的程序员才能轻松阅读和理解第二个版本。
但暂时把可读性放在一边,有什么东西可以让 foldLeft
成为比程序版本更好的选择吗?我可能认为它会更有效,但事实证明 foldLeft的实现实际上就是上面的程序代码。那么这只是一种风格选择,还是有充分的理由使用一个版本或另一个版本?
编辑:需要明确的是,我不是在问其他函数,只是在问foldLeft
。我对使用 foreach
、map
、filter
等非常满意,它们都很好地映射到 for 推导式上。
答案:这里确实有两个很好的答案(由 delnan 和 Dave Griffith),尽管我只能接受一个:
- 使用
foldLeft
因为还有其他优化,例如使用while 循环比 for 循环更快。 - 如果将其添加到常规集合中,请使用折叠,因为这将使向并行集合的转换变得微不足道。
So in reading this question it was pointed out that instead of the procedural code:
def expand(exp: String, replacements: Traversable[(String, String)]): String = {
var result = exp
for ((oldS, newS) <- replacements)
result = result.replace(oldS, newS)
result
}
You could write the following functional code:
def expand(exp: String, replacements: Traversable[(String, String)]): String = {
replacements.foldLeft(exp){
case (result, (oldS, newS)) => result.replace(oldS, newS)
}
}
I would almost certainly write the first version because coders familiar with either procedural or functional styles can easily read and understand it, while only coders familiar with functional style can easily read and understand the second version.
But setting readability aside for the moment, is there something that makes foldLeft
a better choice than the procedural version? I might have thought it would be more efficient, but it turns out that the implementation of foldLeft is actually just the procedural code above. So is it just a style choice, or is there a good reason to use one version or the other?
Edit: Just to be clear, I'm not asking about other functions, just foldLeft
. I'm perfectly happy with the use of foreach
, map
, filter
, etc. which all map nicely onto for-comprehensions.
Answer: There are really two good answers here (provided by delnan and Dave Griffith) even though I could only accept one:
- Use
foldLeft
because there are additional optimizations, e.g. using a while loop which will be faster than a for loop. - Use
fold
if it ever gets added to regular collections, because that will make the transition to parallel collections trivial.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
它更短、更清晰 - 是的,您需要知道折叠是什么才能理解它,但是当您使用 50% 函数式语言进行编程时,您无论如何都应该了解这些基本构建块。折叠正是过程代码所做的事情(重复应用操作),但它被赋予了名称并进行了概括。虽然你正在重新发明的只是一个小轮子,但它仍然是一个轮子的重新发明。
如果 FoldLeft 的实现应该获得一些特殊的好处 - 例如额外的优化 - 您可以免费获得它,而无需更新无数的方法。
It's shorter and clearer - yes, you need to know what a fold is to understand it, but when you're programming in a language that's 50% functional, you should know these basic building blocks anyway. A fold is exactly what the procedural code does (repeatedly applying an operation), but it's given a name and generalized. And while it's only a small wheel you're reinventing, but it's still a wheel reinvention.
And in case the implementation of foldLeft should ever get some special perk - say, extra optimizations - you get that for free, without updating countless methods.
除了对可变变量(甚至可变局部变量)的厌恶之外,在这种情况下使用折叠的基本原因是清晰,偶尔简洁。折叠版本的大部分冗长之处是因为您必须使用带有解构绑定的显式函数定义。如果列表中的每个元素在折叠操作中仅使用一次(常见情况),则可以简化为使用简短形式。因此,数字集合之和的经典定义
比任何等效的命令式构造要简单得多且短得多。
使用函数式收集操作的另一个元原因(尽管在本例中不直接适用)是为了在性能需要时转向使用并行收集操作。折叠不能并行化,但折叠操作通常可以转化为交换关联归约操作,并且这些操作可以并行化。使用 Scala 2.9,利用多个处理核心将某些内容从非并行功能更改为并行功能有时就像将
.par
拖放到要执行并行操作的集合上一样简单。Other than a distaste for mutable variable (even mutable locals), the basic reason to use fold in this case is clarity, with occasional brevity. Most of the wordiness of the fold version is because you have to use an explicit function definition with a destructuring bind. If each element in the list is used precisely once in the fold operation (a common case), this can be simplified to use the short form. Thus the classic definition of the sum of a collection of numbers
is much simpler and shorter than any equivalent imperative construct.
One additional meta-reason to use functional collection operations, although not directly applicable in this case, is to enable a move to using parallel collection operations if needed for performance. Fold can't be parallelized, but often fold operations can be turned into commutative-associative reduce operations, and those can be parallelized. With Scala 2.9, changing something from non-parallel functional to parallel functional utilizing multiple processing cores can sometimes be as easy as dropping a
.par
onto the collection you want to execute parallel operations on.我在这里还没有看到提到的一个词是声明性:
高阶函数 (HOF) 是声明性的关键推动者,因为我们只指定内容(例如“使用这个值集合,将每个值乘以 2,对结果求和”),而不是指定如何(例如初始化累加器、使用 for 循环进行迭代、从集合中提取值、添加到累加器...)。
比较以下内容:
请注意,我们的第一个示例
sumDoubled1
已经比(大多数人认为优于)C/C++/Java<5 for 循环更具声明性,因为我们不必对迭代状态和终止逻辑,但我们仍然容易受到差一错误的影响。接下来,在
sumDoubled2
中,我们基本上处于Java>=5的水平。仍然有一些事情可能会出错,但我们已经非常擅长阅读这种代码形状,因此不太可能出现错误。 但是,不要忘记,在玩具示例中微不足道的模式在扩展到生产代码时并不总是那么可读!使用用于教学目的的去糖化的
sumDoubled3
和惯用的 Scala 版本sumDoubled4
,迭代、初始化、值提取和返回值选择都消失了。当然,学习阅读功能版本需要时间,但我们已经彻底排除了犯错误的选择。 “业务逻辑”被明确标记,并且管道是从其他人正在阅读的同一个菜单中选择的。
One word I haven't seen mentioned here yet is declarative:
Higher-order functions (HOFs) are a key enabler of declarativity, since we only specify the what (e.g. "using this collection of values, multiply each value by 2, sum the result") and not the how (e.g. initialize an accumulator, iterate with a for loop, extract values from the collection, add to the accumulator...).
Compare the following:
Note that our first example,
sumDoubled1
, is already more declarative than (most would say superior to) C/C++/Java<5 for loops, because we haven't had to micromanage the iteration state and termination logic, but we're still vulnerable to off-by-one errors.Next, in
sumDoubled2
, we're basically at the level of Java>=5. There are still a couple things that can go wrong, but we're getting pretty good at reading this code-shape, so errors are quite unlikely. However, don't forget that a pattern that's trivial in a toy example isn't always so readable when scaled up to production code!With
sumDoubled3
, desugared for didactic purposes, andsumDoubled4
, the idiomatic Scala version, the iteration, initialization, value extraction and choice of return value are all gone.Sure, it takes time to learn to read the functional versions, but we've drastically foreclosed our options for making mistakes. The "business logic" is clearly marked, and the plumbing is chosen from the same menu that everyone else is reading from.
值得指出的是,还有另一种调用
foldLeft
的方法,它具有以下优点:对我来说这个版本更清晰,因为我可以看到我将
expr
值折叠到中replacements
集合It is worth pointing out that there is another way of calling
foldLeft
which takes advantages of::
, and is called infix, then the target and parameter are switchedFor me this version is much clearer, because I can see that I am folding the
expr
value into thereplacements
collection