0

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?

Dims
  • 47,675
  • 117
  • 331
  • 600
  • 1
    Why do you want it to be exactly `::`? There is a special semantics for operators that end in `:` in Scala that seem to not match your example. Also your `Nil` will not work that way. – SergGr Jan 26 '18 at 17:15

1 Answers1

2

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 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)…).

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
SergGr
  • 23,570
  • 2
  • 30
  • 51
  • Okay, how to overload operators/function, to get nice builder syntax then? – Dims Jan 26 '18 at 18:34
  • In other words: is it possible to overload list creation syntax so that it creates my of class, not list? – Dims Jan 26 '18 at 18:49
  • Dima, I'm not sure what is not clear to you. You can overload operators but for any typical case you want the overloaded operator not to and with `:`. You should use operators that end in `:` only if it makes sense to build your target object "backwards" as it does for immutable `List`s – SergGr Jan 26 '18 at 19:09