对于包含大量方法链接的Scala代码,什么是公认/推荐的语法?

对于包含大量方法链接的Scala代码,什么是公认/推荐的语法?,scala,syntax,coding-style,method-chaining,Scala,Syntax,Coding Style,Method Chaining,在Scala中,我倾向于编写大型链式表达式,而不是使用val赋值编写许多较小的表达式。在我的公司,我们已经为这种类型的代码发展了一种风格。下面是一个完全人为设计的示例(想法是显示一个包含大量链接调用的表达式): 丹尼尔·斯皮瓦克(Daniel Spiewak)的(pdf),我通常很喜欢,认为链式方法调用中的前导点符号可能不好(参见doc:method Invocation/Higher Order Functions),尽管它没有直接涵盖像这样的多行表达式 是否有另一种更为公认/惯用的方法来编写

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

丹尼尔·斯皮瓦克(Daniel Spiewak)的(pdf),我通常很喜欢,认为链式方法调用中的前导点符号可能不好(参见doc:method Invocation/Higher Order Functions),尽管它没有直接涵盖像这样的多行表达式

是否有另一种更为公认/惯用的方法来编写上述函数
foo

更新日期:2011年6月28日


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

我通常会尽量避免在
map
filter
等方面使用点。所以我可能会这样写:

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

前导点符号可读性很强。我可能会开始使用它。

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

def foo: List[Int] = 
   (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
正如Kevin指出的,无点样式可能会提高简洁性(但可能会损害不熟悉它的开发人员的可读性):


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

该示例有点不切实际,但对于复杂表达式,使用理解通常要简单得多:

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={
val结果=用于{
x10

z我将整个表达式包装到一组括号中,以便对内容进行分组,并尽可能避免点

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

我喜欢大量的
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
s选择了随机名称。 选择
val
s而不是上述其他解决方案有很大的优势:

代码的作用显而易见。在您的示例和大多数其他答案中提到的解决方案中,任何人都不知道它的作用。一个表达式中的信息太多。只有在@Kevin提到的for表达式中,才可以选择说出姓名,但我不喜欢它们,因为:

  • 他们需要更多的代码行
  • 由于模式匹配声明的值(我提到过这一点),它们的速度较慢
  • 这只是我的看法,但我觉得它们看起来很丑

  • 这是即兴表演的方式,你不会出错的

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

    正宗的编译器源代码!

    我还发现前导点符号非常可读,但我不确定这是否只是因为我已经习惯了:)拖尾
    .toList
    是一个轻微的风险,我尝试避免它们,因为它们会干扰分号推断(就像前导点样式一样)@凯文:你能在回答中详细说明这一点吗(也许举个例子)?我知道我在邮件列表上听说过这个问题,但我从来没有亲自提过。评论的要点很好。parens分组、前导点和理解方法都非常好地允许每个操作的评论。我仍然倾向于将
    .toList
    放在parens之外,这对我来说是一个更好的代表代码意图的表示。然后,您可以在不失去太多睡眠的情况下放弃显式键入…我没有考虑使用parens。我想我更喜欢这种样式而不是前导的点表示法,它应该能够巧妙地处理任何分号推断问题。很有意思,但似乎有点像黑客。我更喜欢点,因此更清楚每个lin是什么是的,不必四处寻找几行以外的括号。@luigi-显然,您还需要将缩进和括号结合起来,这样就可以清楚地知道“大画面”中到底发生了什么视觉的clues@Aaron这是我自己喜欢的风格,但无参数方法会给引擎带来麻烦。在这种情况下,最好的方法是将无参数方法应用到的表达式放在括号内,后缀方法用点表示法——就像用
    (1到100)做的那样.view实际上是
    。如果不是
    view
    ,您可以将
    1写入100map{…}etc
    。另一种方法是将后缀作为最后一个方法放在括号内——就像这里的
    toList
    一样。我没想到你可以将
    =
    放入一个括号中进行理解。突然间,它们更有用了。好信息。我喜欢将其作为另一种语法。re:optimizations--我只是加入了愚蠢的额外操作试图人为地延长示例的时间。@overthink-我知道你做到了:)尽管原则仍然有效!我敢不同意下面的Scala专家的观点,并说我更喜欢你的领导。与下面答案中的所有备选方案相比,理解需要发明我认为在很多情况下不必要的名称s、 用括号括起来的-free一词乍一看并不清楚,它是一个列表上的一组操作,而不是一些副作用(我知道,spit)运营。@Paul-领先的
    符号很好,感觉也不错。但是在没有指挥的管弦乐队中表演,或者在没有安全套的情况下进行无偿的性行为也是如此-但是我们不做这些事情有非常明确的原因。我不确定我是否接受这些类比-我觉得你似乎过度紧张了,而事实却很糟糕.notation的侧面是可管理的。您可能还想看看Kevin Wright在这里对我的类似问题的回答:)/refactoring functional scala的布局,其中领先的
    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
    
    def foo: List[Int] =
      ( (1 to 100).view
        map { _ + 3 }
        filter { _ > 10 }
        flatMap { table.get }
        take(3)
        toList )
    
    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
    }
    
    (specMember
      setInfo   subst(env, specMember.info.asSeenFrom(owner.thisType, sym.owner))
      setFlag   (SPECIALIZED)
      resetFlag (DEFERRED | CASEACCESSOR | ACCESSOR | LAZY)
    )