Scala的for推导式:重要特性还是语法糖?

19

当我刚开始学习Scala时,我很喜欢for-comprehensions。它们似乎有点像我从Java 5中习惯的foreach循环,但具有函数式限制和许多优雅的句法美感。

但随着我吸收Scala风格,我发现每次我可以使用for-comprension时,我都会改用map, flatMap, filter, reduceforeach。这种方式对于我的意图来说更加清晰,隐藏的意外也更少,而且通常代码也更短。

据我所知,for-comprehensions总是被编译成这些方法,那么我想知道:它们实际上是用来做什么的?我是否错过了一些函数式启示(这不是第一次)?for- comprehensions是否能够执行其他功能无法完成或者非常繁琐的任务?在特定用例下它们表现突出吗?还是只是个人口味的问题?


2
这几乎是 https://dev59.com/xnNA5IYBdhLWcg3wNa6T 的副本。那个问题想知道 yield 是什么,而这个问题想知道它的目的是什么。答案大致相同。 - Daniel C. Sobral
5个回答

15
请参考这个问题。简短的回答是,for-comprehensions 可以更易读。特别是,如果您有许多嵌套的生成器,实际正在执行的范围变得更加清晰,您不需要大量的缩进。

12

另一个使用for-comprehension的绝佳方式是用于内部DSL。 ScalaQL 是一个很好的例子。它可以将这个转化为:

val underAge = for { 
  p <- Person 
  c <- Company 
  if p.company is c 
  if p.age < 14 
} yield p 

变成这样

SELECT p.* FROM people p JOIN companies c ON p.company_id = c.id WHERE p.age < 14 

还有很多其他内容。


@Erik 我试过了,它可以正常工作。这是一个PDF文件的链接。如果你仍然无法下载该文件,只需在谷歌上搜索:ScalaQL: Language-Integrated Database Queries for Scala 作者:Daniel Spiewak 和 Tian Zhao - Walter Chang
被踩了:实际问题是for-comprehensions是否比它们的desugaring更有优势-而且你也可以用desugared形式编写该代码。 ScalaQuery是一个更好的例子,因为它有一个可用的生产质量实现。那篇论文忽略了太多重要细节,不能推荐。 - Blaisorblade

10

for-comprehensions 是语法糖,但这并不意味着它们不重要。通常情况下,它们比展开形式更为简洁,这很好,但更重要的是,它们帮助那些使用命令式语言的程序员使用函数式的构造。

当我刚开始学习 Scala 时,我经常使用 for-comprehensions,因为它们很熟悉。然后我几乎完全停止使用,因为我觉得使用底层方法更加明确和清晰。现在我又回到了使用 for-comprehensions,因为我觉得它们更好地表达了我在做什么的意图,而不只是手段。


5
在某些情况下,for推导式可以更好地表达意图,因此当它们能够做到时,请使用它们。
还要注意,使用for推导式时,您可以免费获得模式匹配。例如,使用for推导式迭代Map要简单得多: for ((key, value) <- map) println (key + "-->" + value) 而不是使用foreach: map foreach { case (key, value) => println (key + "-->" + value) }

3

你说得对。for循环推导式只是一种语法糖。我相信一旦你习惯了底层方法,它们更加简洁易读。

比较以下等效语句:

1. for (i <- 1 to 100; if (i % 3 == 0)) yield Math.pow(i, 2)
2. (1 to 100).filter(_ % 3 == 0).map(Math.pow(_, 2))

在我看来,选项1中添加分号会分散注意力,让人感觉这是多个语句的组合。同时,变量i的值不确定(是1还是99或介于两者之间?)这也破坏了原本良好的风格。
选项2更明显是一系列对象方法调用的链条。每个链接都清楚地说明了其职责,没有中间变量。
也许包括for comprehensions只是为了方便从Java过渡到Scala的开发人员。无论选择哪种方式,都是风格和个人偏好的问题。

"一旦你习惯了它们" - 没错。一旦我理解了单子,我就切换到了底层方法。在那之前,for 推导式是一个有用的链接。 - Marcus Downing
1
你在1中根本不需要分号:for (i <- 1 to 100 if (i % 3 == 0)) yield Math.pow(i, 2)。此外,你可以用大括号替换圆括号来激活分号推断。 - Blaisorblade
谢谢@Blaisorblade,我想知道在2009年是否需要分号。 - Synesso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接