Reputation: 9922
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
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
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