如何在 Scala 中重载双冒号运算符?

How to overload two-colon operator in Scala?

我希望通过重载列表运算符和 NilScala 中实现 builder pattern。但是显然没有用。

class SomeBuilder {

  val sb : java.lang.StringBuffer = new java.lang.StringBuffer

  def ::(str : java.lang.String): SomeBuilder = {
    sb.append(str)
    this
  }

  def Nil(): java.lang.String = {
    sb.toString
  }

}

object Hello extends App {
  println( new SomeBuilder :: "aaa" :: "bbb" :: Nil )
}

为什么以及如何成功?

您可能会在 Scala spec

中找到

The associativity of an operator is determined by the operator's last character. Operators ending in a colon ‘:’ are right-associative. All other operators are left-associative.

稍后:

If there are consecutive infix operations e0;op1;e1;op2…opn;en with operators op1,…,opn of the same precedence, then all these operators must have the same associativity. If all operators are left-associative, the sequence is interpreted as (…(e0;op1;e1);op2…);opn. Otherwise, if all operators are right-associative, the sequence is interpreted as e0;op1;(e1;op2;(…opn;en)…).

这意味着你的语法

new SomeBuilder :: "aaa" :: "bbb" :: Nil 

实际解释为

Nil.::("bbb").::("aaa").::(new SomeBuilder)

这样做是因为 :: 是函数式编程中传统上用于构建不可变 List 的运算符,而这正是那里所需的语义。所以如果你真的想使用 :: 你应该写这样的代码:

object Nil {

 class RealBuilder(val sb: java.lang.StringBuilder) {
    def ::(str: java.lang.String): RealBuilder = {
      sb.append(str.reverse)
      this
    }

    def ::(terminal: SomeBuilder): String = {
      sb.reverse.toString
    }

    override def toString = sb.toString
  }


    override def toString = sb.toString
  }

  def ::(str: java.lang.String): RealBuilder = {
    new RealBuilder(new java.lang.StringBuilder(str))
  }

  override def toString = ""
}

sealed trait SomeBuilder
object SomeBuilder extends SomeBuilder

然后

println(SomeBuilder :: "aaa" :: "bbb" :: Nil)

会起作用。但请注意这是多么低效:实际上你将每个字符串旋转了两次。您必须这样做,因为 :: 是右结合的,并且没有有效的 perpend 方法。

总结 您可以使用此语法,但是为此使用 :: 是一个非常糟糕的主意。如果你几乎使用任何其他东西,你会更好。

旁注 #1:使用 Nil 也是一个相当糟糕的主意,因为它会与 scala.collection.immutable.Nil(即空 List)。

旁注 #2:虽然现代 JVM 实现可以优化 synchronized 很多情况下你最好使用 java.lang.StringBuilder instead of java.lang.StringBuffer 在这样的非多线程环境下


更新相同但 ++

所以主要问题是使用 ::。如果您使用 ++ 等其他运算符,它应该可以找到。

如果这样的语法

println(new SomeBuilder ++ "aaa1" ++ "bbb2" build)

println((new SomeBuilder ++ "aaa1" ++ "bbb2").build)

你没问题,你可以使用这样的代码:

class SomeBuilder {
  val sb: StringBuilder = new StringBuilder

  def ++(s: String): SomeBuilder = {
    sb.append(s)
    this
  }

  def build: String = sb.toString()

  override def toString = sb.toString
}

如果您出于某种原因更喜欢更接近您的示例的内容,例如

println(new SomeBuilder ++ "aaa1" ++ "bbb2" ++ BuildString)

您可以使用这样的代码:

class SomeBuilder {
  val sb: StringBuilder = new StringBuilder

  def ++(s: String): SomeBuilder = {
    sb.append(s)
    this
  }

  def ++(terminator: BuildString): String = sb.toString()

  override def toString = sb.toString
}

sealed trait BuildString

object BuildString extends BuildString