0

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
  }

}
BinderNet
  • 47
  • 7
  • 1
    The process of finding and applying implicit conversions is driven by a *failure* of the expression written to type check due to the method invoked not existing in the statically known type of expression to which it's applied. Since String has a suitable `equals`, implicit conversion can never be used to replace it with one of your own. – Randall Schulz Jan 21 '14 at 18:13
  • Thanks @Randall, that's the conclusion I'd arrived at. – BinderNet Jan 21 '14 at 20:00
  • Thanks all for your responses. Conclusion is that Java String is final, and there's no workaround to string equality other than to define another method which will be called by implicit conversion. – BinderNet Jan 23 '14 at 09:37

2 Answers2

0

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.

fotNelton
  • 3,844
  • 2
  • 24
  • 35
  • Thanks @fotNelton. I'm only implementing an existing DSL so adding the :BString hint will be tricky. Adding strings to ints etc is a code smell, you're right, but it's what I'm trying to emulate. – BinderNet Jan 21 '14 at 20:04
  • Yes, having to manually add `:BString` really would be frustrating. Like suggested in the other answer, and also in my opinion, there's no easy way around the issue because if you write `"1.1" == y`, how is the compiler supposed to know that you want the `equals` method of `BString` since you're clearly calling it on `String`. One other thing that I could think of is a postfix operator like `.b` that converts regular `String`s to `BString`s (compare this to the way `RegEx`s provide this in Scala). – fotNelton Jan 22 '14 at 07:08
0

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.

Community
  • 1
  • 1
rxg
  • 3,777
  • 22
  • 42