因此,在阅读这个问题时,有人指出,而不是程序代码:
def expand(exp: String, replacements: Traversable[(String, String)]): String = {
var result = exp
for ((oldS, newS) <- replacements)
result = result.replace(oldS, newS)
result
}
Run Code Online (Sandbox Code Playgroud)
您可以编写以下功能代码:
def expand(exp: String, replacements: Traversable[(String, String)]): String = {
replacements.foldLeft(exp){
case (result, (oldS, newS)) => result.replace(oldS, newS)
}
}
Run Code Online (Sandbox Code Playgroud)
我几乎肯定会写第一个版本,因为熟悉程序或功能样式的编码人员可以轻松阅读和理解它,而只有熟悉功能样式的编码人员才能轻松阅读和理解第二个版本.
但暂时将可读性放在一边,有没有foldLeft
比程序版更好的选择呢?我可能认为它会更有效率,但事实证明,foldLeft的实现实际上只是上面的过程代码.那么它只是一种风格选择,还是有充分的理由使用一个版本或另一个版本?
编辑:为了清楚,我不是在询问其他功能foldLeft
.我与使用非常开心foreach
,map
,filter
等,这些都很好地映射到换内涵.
答:实际上有两个很好的答案在这里(提供delnan和戴夫·格里菲斯),即使我只能接受一个:
foldLeft
是因为还有其他优化,例如使用一个比for循环更快的while循环.fold
如果它被添加到常规的集合,因为那样会使平行收藏琐碎的转变.小智 16
它更短更清晰 - 是的,您需要知道什么是折叠才能理解它,但是当您使用50%功能的语言进行编程时,您应该知道这些基本构建块.折叠正是程序代码所做的(重复应用操作),但它给出了一个名称并进行了概括.虽然它只是一个小轮子你重新发明,但它仍然是一个轮子重塑.
如果foldLeft的实现应该获得一些特殊的好处- 比如额外的优化 - 你可以免费获得它,而无需更新无数的方法.
除了对可变变量(甚至是可变的本地人)的厌恶之外,在这种情况下使用折叠的基本原因是清晰度,偶尔简洁.fold版本的大部分含义是因为你必须使用带有解构绑定的显式函数定义.如果列表中的每个元素在折叠操作中仅使用一次(常见情况),则可以简化为使用缩写形式.因此,经典定义了一组数字的总和
collection.foldLeft(0)(_+_)
Run Code Online (Sandbox Code Playgroud)
比任何等效的命令式构造更简单,更短.
使用功能集合操作的另一个元原因虽然在这种情况下不能直接应用,但是如果性能需要,可以使用并行集合操作.折叠无法并行化,但折叠操作通常可以转换为可交换关联的减少操作,并且可以并行化.使用Scala 2.9,使用多个处理核心将某些东西从非并行功能更改为并行功能有时可以像放入.par
要执行并行操作的集合一样简单.
我还没有看到这里提到的一个词是陈述性的:
声明性编程通常被定义为任何非必要的编程风格.存在许多其他常见定义,试图将该术语定义为除了简单地将其与命令式编程进行对比之外的定义.例如:
- 该程序描述了计算应执行并没有如何计算它
- 任何缺乏副作用的编程语言(或者更具体地说,是引用透明的)
- 与数学逻辑明确对应的语言.
这些定义基本上重叠.
高阶函数(HOF)是声明性的关键推动因素,因为我们只指定了什么(例如"使用这个值集合,将每个值乘以2,求和结果")而不指定如何(例如初始化累加器,使用for循环迭代,从集合中提取值,添加到累加器...).
比较以下内容:
// Sugar-free Scala (Still better than Java<5)
def sumDoubled1(xs: List[Int]) = {
var sum = 0 // Initialized correctly?
for (i <- 0 until xs.size) { // Fenceposts?
sum = sum + (xs(i) * 2) // Correct value being extracted?
// Value extraction and +/* smashed together
}
sum // Correct value returned?
}
// Iteration sugar (similar to Java 5)
def sumDoubled2(xs: List[Int]) = {
var sum = 0
for (x <- xs) // We don't need to worry about fenceposts or
sum = sum + (x * 2) // value extraction anymore; that's progress
sum
}
// Verbose Scala
def sumDoubled3(xs: List[Int]) = xs.map((x: Int) => x*2). // the doubling
reduceLeft((x: Int, y: Int) => x+y) // the addition
// Idiomatic Scala
def sumDoubled4(xs: List[Int]) = xs.map(_*2).reduceLeft(_+_)
// ^ the doubling ^
// \ the addition
Run Code Online (Sandbox Code Playgroud)
请注意,我们的第一个示例sumDoubled1
已经比循环中的C/C++/Java <5更具声明性(大多数人会说优于)C/C++/Java <5,因为我们不必对迭代状态和终止逻辑进行微观管理,但我们仍然容易受到攻击逐个错误.
接下来,sumDoubled2
我们基本上处于Java> = 5的水平.还有一些问题可能会出错,但我们在阅读这种代码形式方面已经相当不错,因此错误的可能性很小. 但是,不要忘记,在扩展到生产代码时,玩具示例中的微不足道的模式并不总是那么可读!
随着sumDoubled3
教学目的的消亡,以及sumDoubled4
惯用的Scala版本,迭代,初始化,值提取和返回值的选择都消失了.
当然,学习阅读功能版本需要时间,但我们已经大大取消了我们犯错误的选择."业务逻辑"已清楚标记,管道从其他人正在阅读的相同菜单中选择.