Feuermurmel
Feuermurmel

Reputation: 9922

Overload plus `+` operator on String

In the following example, I'm trying to declare a class WrappedString, which simply wraps a String [1]. I'd like to be able to concatenate such instances with the + operator and have plain String instances automatically converted to WrappedString instances when necessary.

So there are three possible applications of the + operator which should return a WrappedString instance, illustrated with calls to println() at the bottom of the example.

import scala.language.implicitConversions

object test extends App {
  case class WrappedString(value: String) {
    override def toString = s"[$value]"
    def +(right: WrappedString) = WrappedString(value + right.value)
  }

  object WrappedString {
    implicit def fromString(value: String) = WrappedString(value)
  }

  implicit class StringExtensions(value: String) {
    // Isn't actually used in the code below.
    def +(right: WrappedString) = WrappedString(value) + right
  }

  println(WrappedString("a") + WrappedString("b")) // [ab]
  println(WrappedString("a") + "b") // [ab]

  // Would like this to print `[ab]`
  println("a" + WrappedString("b")) // a[b]
}

For some reason, the 3rd example prints a[b]. The WrappedString instance is converted to a String before being concatenated with the string "a" to produce the final result.

How can I change the declarations of WrappedString and/or the implicits so that in the 3rd example, the String is converted to a WrappedString before the + operator is applied?

[1]: In the project I'm working on, the wrapped string carries formatting information in the form of ANSI SGR escape codes.

Upvotes: 1

Views: 863

Answers (2)

jwvh
jwvh

Reputation: 51271

The compiler will never choose an implicit + method over an explicit + method, which is something the String class has, so "a" + WrappedString("b") will always become "a".+(WrappedString("b").toString).

To force the conversion you could pick a different method name, one that String doesn't have, along with an implicit class, which means it can't be a case class. That way you don't need the implicitConversions import, which is discouraged.

implicit class WrappedString(val value: String) {
  override def toString = s"[$value]"
  def +#(right: WrappedString) = WrappedString(value + right.value)
}

WrappedString("a") +# WrappedString("b") // [ab]
WrappedString("a") +# "b" // [ab]
"a" +# WrappedString("b") // [ab]
"a" +# "b" // also [ab]

It might also be helpful if you didn't use a class name that already exists in the Standard Library.

Upvotes: 1

Dennis Hunziker
Dennis Hunziker

Reputation: 1293

I'm wondering where you actually want the implicit conversion as you're manually wrapping all the values in WrappedString yourself. It seems you need something like a right-associative operator which Scala support using the : at the end of the function name.

For example:

case class WrappedString(value: String) {
    override def toString = s"[$value]"
    def +(right: WrappedString) = WrappedString(value + right.value)
    def +:(right: WrappedString) = right + WrappedString(value)
}

println("a" +: WrappedString("b")) // [ab]

Other than that you could force the conversion by specifying the type like:

println(("a": WrappedString) +: WrappedString("b")) // [ab]

Or possibly just move this to a separate function. Happy to be corrected but how else would the compiler ever know what your intention is as a String already has a valid + function and exploring all possible conversions wouldn't be very efficient.

Upvotes: 0

Related Questions