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

时间:2018-01-26 16:59:49

标签: scala list operator-overloading builder-pattern

我希望通过重载列表运算符和ScalaNil中实现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 )
}

为什么以及如何成功?

1 个答案:

答案 0 :(得分:2)

正如您在Scala spec

中找到的那样
  

操作员的关联性由操作员的最后一个字符决定。以冒号':'结尾的运算符是右关联的。所有其他运算符都是左关联的。

稍后:

  

如果连续的中缀操作e0;op1;e1;op2…opn;en具有相同优先级的运算符op1,…,opn,则所有这些运算符必须具有相同的关联性。如果所有运算符都是左关联的,则序列被解释为(…(e0;op1;e1);op2…);opn。否则,如果所有运算符都是右关联的,则序列将被解释为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方法。

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

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

Sidenote#2 :虽然现代JVM实施可以优化synchronized,但在很多情况下,您最好使用java.lang.StringBuilder代替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