Как перегрузить двухколонный оператор в Scala?

Я хотел реализовать шаблон строителя в Scala перегружая оператор списка и Nil, Но, видимо, это не сработало.

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 ответ

Решение

Как вы можете найти в спецификации Scala

Ассоциативность оператора определяется последним символом оператора. Операторы, оканчивающиеся двоеточием ':', ассоциативны справа. Все остальные операторы левоассоциативны.

и чуть позже:

Если есть последовательные инфиксные операции 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).

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
Другие вопросы по тегам