Question

I'm writing a DSL where I ultimately want to be able to have my own string type where I can do things like

var s:BString = "123"
if ("123" == s) ...

and also

var d:Double = s + 5.0

I have addition basically working with implicit conversions along with 5.0 + s

I also have == working one way by overriding the equals method in my 'BString' class ,where the first argument (left side) is a BString.

The problem is somehow overriding the Java String equals. I had a look at the String.equals() source code, and the equals method takes a Java Object which I can't seem to get working with implicit conversions. The equals method there then casts the object to a string, so I think unless I have a subclass of (final) String I'm SOL.

Is there any other approach? Ideas?

class BString(val string: String) {
    override def toString() = string
    def toDouble:Double = string.toDouble
    def +(bs:BString) = string.toDouble + bs.toDouble
    def +(d:Double) = string.toDouble + d
    override def equals(x$1:Any):Boolean = string == x$1.toString
} 

object Test {

  implicit def string2BString(x:String)  = new BString(x)
  implicit def double2BString(x:Double)  = new BString(x.toString)
  implicit def bString2Object(x:BString) = { // never being reached
    println("OO");
    x.asInstanceOf[Object]
  } 

  def main(args: Array[String]) {

    var y:BString = "1.1"
    println(y + 1.1)        //2.2
    println(1.2 + y)        //2.3
    println("1.1" == y)     // false :-(
    println("1.1" equals y) // false :-(
    println("1.1" equals bString2Object(y)) // also false
    println(y == "1.1")     // true
  }

}
Was it helpful?

Solution

String is a final class in the JDK for lots of reasons - basically you could compromise the entire security model of the JVM if you were allowed to fiddle with its equals method! (See this question for detailed reasons.)

That being so, the best you will be able to do in your DSL is introduce a new operator that is not defined for String, for example ===, and make sure you have an implicit conversion available for String to BString:

class BString(val string: String) {
  def ===(other: BString) = TODO
}

object BString {
  implicit def string2BString(x: String) = new BString(x)
}

You might also consider making your BString class final as well. Writing a correct equals method can be difficult or impossible in the face of inheritance - think about the asymmetry that you have already witnessed when trying to compare String and Object, and check out this old article for a thorough treatment of the problem.

OTHER TIPS

As much as I don't like implementing language behavior like adding strings to doubles (and vice versa), here's what you could do:

import scala.language.implicitConversions

class BString(val string: String) {
  def toDouble: Double = string.toDouble
  def +(bs: BString): Double = string.toDouble + bs.toDouble
  def +(d: Double): Double = string.toDouble + d
  override def equals(other: Any) = other match {
    case bs: BString => string == bs.string
    case os: String  => string == os
    case _           => false
  }
}

object BString {
  implicit def string2BString(x: String) = new BString(x)
  implicit def double2BString(d: Double) = new BString(d.toString)
}

object Test extends App {
  val y: BString = "1.1"
  println(y + 1.1)                   // 2.2
  println(1.2 + y)                   // 2.3
  println(("1.1": BString) == y)     // true
  println(("1.1": BString) equals y) // true
  println(y == "1.1")                // true
}

As you can see, I've changed both the definition of equals such that it pattern matches on the runtime type of its argument, and I've also given the typer a hint that "1.1" is really a BString by writing ("1.1": BString).

EDIT: Also note that you actually don't need the def +(d: Double) method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top