【问题标题】:Scala toString: parenthesize or not?Scala toString:括号与否?
【发布时间】:2011-12-28 18:31:28
【问题描述】:

我希望这个线程是某种优缺点的总结,用于覆盖和调用 toString 带或不带空括号,因为这件事有时仍然让我感到困惑,即使我已经进入 Scala 很长时间了一会儿。

那么哪一个比另一个更可取?非常感谢 Scala 极客、官员和强迫症偏执狂的评论。

toString 的优点:

  • 乍一看似乎是一个显而易见的自然选择;
  • 大多数情况都是微不足道的,只是在不修改内部状态的情况下即时构造字符串;
  • 另一种常见的情况是将方法调用委托给包装的抽象:

    override def toString = underlying.toString
    

toString() 的优点:

  • 绝对不是“类似访问器”的名称(这就是 IntelliJ IDEA 检查员不时抱怨的方式);
  • 可能意味着一些 CPU 或 I/O 工作(在计算每个 System.arrayCopy 调用对性能至关重要的情况下);
  • 甚至可能意味着一些可变的状态更改(考虑第一个 toString 调用成本高昂的示例,因此它在内部缓存以在未来产生更快的调用)。

那么最佳做法是什么?我还缺少什么吗?

更新:这个问题与在每个 JVM 对象上定义的toString 专门相关,所以我希望找到最佳实践,如果它存在的话.

【问题讨论】:

标签: scala


【解决方案1】:

这个答案没有太多争论,但 GenTraversableOnce 单独声明了以下不带括号的定义:

toArray
toBuffer
toIndexedSeq
toIterable
toIterator
toList
toMap
toSeq
toSet
toStream
toTraversable

【讨论】:

  • 感谢您的列表。我认为应该有某种约定来更喜欢这种无副作用方法的命名,从而消除有关空括号的问题(IntelliJ 人员可能会为此添加检查)
  • @incarnate 很有趣。我遇到你的问题的原因正是因为相关的 IntelliJ 想法检查:它一直标记 toString,有时带括号,有时不带括号(似乎取决于下一个父级使用的任何内容),我想看看约定应该是。至少在 Scala 库中保持一致性会很好,例如对于 FunctionN (有括号)和 Iterator (没有)。库中的一致性将消除检查的很多麻烦,否则这些检查通常是非常好的。
【解决方案2】:

我建议始终使用toString。关于你的第三个“亲”到toString()

可能意味着一些可变的状态改变(考虑一个例子,第一次 toString 调用很昂贵,所以它被内部缓存以在未来产生更快的调用)。

首先,toString 通常不应该是一项昂贵的操作。但是假设它很昂贵,并且假设您确实选择在内部缓存结果。即使在这种情况下,我也会说使用toString,只要toString结果对于对象的给定状态始终相同(忽略toString 的状态)缓存)。

我不建议在没有括号的情况下使用toString 的唯一原因是,如果您有一个代码分析器/分析器,它会根据括号的存在与否做出假设。在这种情况下,请遵循所述分析器规定的约定。此外,如果您的toString 如此复杂,请考虑将其重命名为其他名称,例如expensiveToString。在大多数情况下,非正式的预期 toString 是一个简单明了的函数。

【讨论】:

    【解决方案3】:

    Scala 中的编程(第 10.3 节)是这样说的:

    推荐的约定是在任何时候都使用无参数方法 没有参数,并且该方法仅通过以下方式访问可变状态 读取包含对象的字段(特别是,它不 改变可变状态)。该约定支持统一访问 原则,1 表示客户端代码不应受 决定将属性实现为字段或方法。

    这是(非官方的)Scala 风格指南(第 18 页)所说的:

    Scala 允许在 arity-0 方法上省略括号(不 论据):

    reply() 
    // is the same as 
    reply 
    

    但是,这种语法 仅应在所讨论的方法没有副作用时使用 (纯功能)。换句话说,省略是可以接受的 调用 queue.size 时带括号,但调用 println() 时不带括号。 这个约定反映了上面给出的方法声明约定。

    后者没有提到统一访问原则。

    如果您的toString 方法可以实现为val,则意味着该字段是不可变的。然而,如果你的类是可变的,toString 可能不会总是产生相同的结果(例如StringBuffer)。所以在 Scala 中编程意味着我们应该在两种不同的情况下使用toString()

    1) 当它的值是可变的时候

    2) 有副作用时

    我个人认为忽略第一个更为常见和一致。在实践中toString 几乎不会有副作用。所以(除非确实如此),请始终使用toString 并忽略统一访问原则(遵循样式指南):保留括号以表示副作用,而不是可变性。

    【讨论】:

    • 感谢您的详尽解释,我接受了这个。我实际上同意可变性和副作用具有不同的语义。问题是是否将内部缓存解释为副作用(例如在并发敏感的上下文中)。无论如何,我认为这是我正在寻找的确切解释。再次感谢!
    • @incarnate 关于缓存,我不认为更改检索结果所花费的时间长度通常被认为是副作用(尽管您可以相信它是),因为函数不会对此作出承诺。
    【解决方案4】:

    是的,你错过了一些东西:语义。

    如果您有一个简单地返回值的方法,则不应使用括号。原因是这模糊了vals 和defs 之间的界限,满足了Uniform Access Principle。例如。考虑集合的size 方法。对于固定大小的向量或数组,这可以只是一个val,其他集合可能需要计算它。

    空括号的使用应仅限于执行某种副作用的方法,例如println(),或者增加内部计数器的方法,或者重置连接的方法等。

    【讨论】:

    • 感谢您的回复。我很确定我知道空括号和无参数方法之间的区别以及统一访问背后的语义。我试图找出是否可以制定一些严格的规则,例如“始终使用toString”或“从不使用toString()”。如果您建议始终坚持使用 UAP 并使用toString,那么请向我解释我的问题中的最后一个案例(关于内部缓存结果)——为什么内部缓存不是副作用?
    • 如果无法区分一个值是缓存还是重新计算,则无法观察到副作用,因此实际上并没有发生。即使在 Haskell 中,您也可以使用可变状态,只要它被很好地包装并且因此永远不可观察。我认为我们应该在 Scala 中更加宽容,忽略不影响程序流程的副作用(例如日志记录)。
    猜你喜欢
    • 2016-08-03
    • 2016-12-29
    • 2015-10-21
    • 1970-01-01
    • 1970-01-01
    • 2015-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多