I was wishing to implement builder pattern in Scala
by overloading list operator and Nil
. But apparently it didn't work.
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 )
}
Why and how to succeed?
As you may find in the 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.
and a bit later:
If there are consecutive infix operations
e0;op1;e1;op2…opn;en
with operatorsop1,…,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 ase0;op1;(e1;op2;(…opn;en)…)
.
It means that your syntax
new SomeBuilder :: "aaa" :: "bbb" :: Nil
is actually interpreted as
Nil.::("bbb").::("aaa").::(new SomeBuilder)
This is done that way because ::
is an operator traditionally used in functional programming to build immutable List
and this is exactly the semantics required there. So if you really want to use ::
you should make a code like this:
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
and then
println(SomeBuilder :: "aaa" :: "bbb" :: Nil)
will work. But note how dreadfully inefficient this is: effectively you rotate every string twice. You have to do this because ::
is right-associative and there is no efficient perpend
method.
Sum up you can make this syntax work but this is a really bad idea to use ::
for that. You will be better if you use literally almost anything else.
Sidenote #1: Using Nil
is also a rather bad idea because it will clash with the scala.collection.immutable.Nil
(i.e. an empty List
).
Sidenote #2: Although modern JVM implementation can optimize synchronized
out it many cases you better use java.lang.StringBuilder
instead of java.lang.StringBuffer
in such non-multithread environment
Update the same but with ++
So the main problem was using ::
. If you use other operator like ++
, it should work find.
If syntax like that
println(new SomeBuilder ++ "aaa1" ++ "bbb2" build)
or
println((new SomeBuilder ++ "aaa1" ++ "bbb2").build)
is OK with you, you may use code like this:
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
}
if you for some reason prefer something closer to your example such as
println(new SomeBuilder ++ "aaa1" ++ "bbb2" ++ BuildString)
you may use code like this:
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