Scala代码的接受/推荐语法是什么,有很多方法链接?

时间:2011-06-24 18:05:15

标签: scala syntax coding-style method-chaining

在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),我一般都喜欢,它表明链式方法调用中的前导点符号可能不好(参见doc:Method Invocation / Higher-Order Functions),尽管它没有直接覆盖这样的多行表达式。

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

更新:2011年6月28日

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

6 个答案:

答案 0 :(得分: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

答案 1 :(得分:11)

我将整个表达式包装成一组括号,以便在可能的情况下分组并避免使用点,

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

答案 2 :(得分:6)

以下是临时性的方法。你不能出错。

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

正宗的编译源!

答案 3 :(得分:5)

我更喜欢val s:

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
}

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

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

  1. 他们需要更多代码行
  2. 由于模式匹配声明的值,它们会变慢(我提到了这个here)。
  3. 只是我的意见,但我觉得它们看起来很难看

答案 4 :(得分:2)

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

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

凯文指出,无点样式可能会改善简洁性(但对于不熟悉它的开发人员可能会损害可读性):

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

如果由于长度需要将表达式分隔为多行,则前导点表示法是完全可以接受的。使用这种表示法的另一个原因是当操作需要单独的注释时。如果你需要在多行上传播一个表达式,由于它的长度或者需要对单个操作进行注释,最好将整个表达式包含在parens中。 (作为Alex Boisvert suggests。在这些情况下,每个(逻辑)操作应该在它自己的行上(即每个操作都在一行上,除非可以通过单个注释简洁地描述多个连续操作):

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

这种技术避免了在表达式结尾处使用前导点表示法或调用0-arg方法时可能出现的潜在semicolon inference issues

答案 5 :(得分:1)

我通常会尽量避免在mapfilter等内容中使用点。所以我可能会写如下:

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

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