Как перегрузить двухколонный оператор в 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