23

在 Scala 中,我倾向于编写大型链式表达式,而不是许多带有val赋值的较小表达式。在我的公司,我们已经为这种类型的代码发展了一种风格。这是一个完全人为的例子(想法是显示一个包含大量链接调用的表达式):

import scala.util.Random
val table = (1 to 10) map { (Random.nextInt(100), _) } toMap

def foo: List[Int] =
  (1 to 100)
    .view
    .map { _ + 3 }
    .filter { _ > 10 }
    .flatMap { table.get }
    .take(3)
    .toList

Daniel Spiewak 的Scala Style Guide (pdf),我通常喜欢它,它表明链式方法调用中的前导点表示法可能不好(参见文档:方法调用/高阶函数),尽管它不包括多行表达式直接这样。

是否有另一种更被接受/惯用的方式来编写foo上面的函数?

更新:2011 年 6 月 28 日

下面有很多很棒的答案和讨论。似乎没有 100% 的“你必须这样做”的答案,所以我将通过投票接受最受欢迎的答案,这是目前的理解方法。就个人而言,我认为我现在将坚持使用前导点符号并接受随之而来的风险。

4

6 回答 6

16

这个例子有点不切实际,但对于复杂的表达式,使用推导式通常要干净得多:

def foo = {
  val results = for {
    x <- (1 to 100).view
    y = x + 3 if y > 10
    z <- table get y
  } yield z
  (results take 3).toList
}

这里的另一个优点是您可以命名计算的中间阶段,并使其更具自我记录性。

如果简洁是你的目标,这可以很容易地变成单行(无点风格在这里有帮助):

def foo = (1 to 100).view.map{3+}.filter{10<}.flatMap{table.get}.take(3).toList
//or
def foo = ((1 to 100).view map {3+} filter {10<} flatMap {table.get} take 3).toList

和往常一样,尽可能优化您的算法:

def foo = ((1 to 100).view map {3+} filter {10<} flatMap {table.get} take 3).toList
def foo = ((4 to 103).view filter {10<} flatMap {table.get} take 3).toList
def foo = ((11 to 103).view flatMap {table.get} take 3).toList
于 2011-06-24T19:44:10.517 回答
11

我将整个表达式包装到一组括号中,以对事物进行分组并尽可能避免使用点,

def foo: List[Int] =
  ( (1 to 100).view
    map { _ + 3 }
    filter { _ > 10 }
    flatMap { table.get }
    take(3)
    toList )
于 2011-06-24T19:58:34.697 回答
6

以下是即兴的做法。你不能出错。

(specMember
  setInfo   subst(env, specMember.info.asSeenFrom(owner.thisType, sym.owner))
  setFlag   (SPECIALIZED)
  resetFlag (DEFERRED | CASEACCESSOR | ACCESSOR | LAZY)
)

正宗的编译源!

于 2011-06-27T09:11:59.590 回答
5

我更喜欢很多vals:

def foo = {
  val range = (1 to 100).view
  val mappedRange = range map { _+3 }
  val importantValues = mappedRange filter { _ > 10 } flatMap { table.get }
  (importantValues take 3).toList
}

因为我不知道你想用你的代码做什么,所以我为vals 选择了随机名称。val选择s 而不是其他提到的解决方案有一个很大的优势:

很明显你的代码做了什么。在您的示例和大多数其他答案中提到的解决方案中,任何人乍一看都不知道它的作用。一个表达式包含的信息太多。只有在@Kevin 提到的 for 表达式中,才能选择说出名字,但我不喜欢它们,因为:

  1. 他们需要更多的代码行
  2. 由于声明值的模式匹配,它们速度较慢(我在这里提到过)。
  3. 只是我的看法,但我认为它们看起来很丑
于 2011-06-25T09:49:39.563 回答
2

我的规则:如果表达式适合单行(80-120 个字符),请将其保留在一行并尽可能省略点:

def foo: List[Int] = 
   (1 to 100).view map { _ + 3 } filter { _ > 10 } flatMap table.get take 3 toList

正如 Kevin 所指出的,无点风格可能会提高简洁性(但可能会损害不熟悉它的开发人员的可读性):

def foo: List[Int] = 
   (1 to 100).view map{3+} filter{10<} flatMap table.get take 3 toList

如果由于长度需要将表达式分隔为多行,则前导点表示法是完全可以接受的。使用此符号的另一个原因是当操作需要单独的注释时。如果您需要将表达式分散到多行,由于它的长度或需要注释单个操作,最好将整个表达式包装在括号中(正如Alex Boisvert 所建议的那样。在这些情况下,每个(逻辑)操作都应该继续它自己的行(即每个操作都在一行上,除非多个连续操作可以通过单个注释简洁地描述):

def foo: List[Int] = 
   ( (1 to 100).view
     map { _ + 3 }
     filter { _ > 10 }
     flatMap table.get
     take 3
     toList )   

此技术避免了在使用前导点表示法或在表达式末尾调用 0-arg 方法时可能出现的潜在分号推断问题。

于 2011-06-24T19:36:18.700 回答
1

我通常会尽量避免将 dot 用于mapfilter. 所以我可能会这样写:

def foo: List[Int] =
  (1 to 100).view map { x =>
    x + 3 } filter { x =>
    x > 10 } flatMap { table.get } take(3) toList

前导点符号非常易读。我可能会开始使用它。

于 2011-06-24T18:15:24.310 回答