3

I want to write a class which can work nicely with numbers and operators, so I want to know how to overload an operator when the left hand argument is a built-in type or other value for which I can't modify the implementation.

class Complex(val r:Double,val i:Double){
  def +(other:Complex) = new Complex(r+other.r,i+other.i)
  def +(other:Double) = new Complex(r+other,i)
  def ==(other:Complex) = r==other.r && i==other.i
}

With this example, the following works:

val c = new Complex(3,4)
c+2 == new Complex(5,4)  //true

But I also want to be able to write

2+c

which doesn't work as Int.+ can't accept an argument of type Complex. Is there a way to let me write this and have it work as I want?

I've found out about right associative methods, but they seem to require a different operator name.

Tesseract
  • 526
  • 3
  • 20

3 Answers3

6

You can add an implicit conversion from Int to Complex:

implicit def intToComplex(real: Int) = new Complex(real, 0)

You can read about implicits (e.g. here: Understanding implicit in Scala). The short version is that, if your program doesn't type check, all implicits that are in the current scope will be tried and if some of them works, it will be applied by the compiler.

So, when you write 2 + c, the compiler doesn't find an operator + that takes a complex in Int, so it tries the implicits. It will then compile 2 + c to intToComplex(2) + c which will work correctly.

Community
  • 1
  • 1
Ivan Vergiliev
  • 3,771
  • 24
  • 23
3

Using implicit conversions to turn Int into Complex as mentioned before will do the job.

Here's a working solution which puts it all together to complement Ivan's answer:

import scala.language.implicitConversions

class Complex(val real:Double, val imaginary:Double){
    def +(other:Complex) = new Complex(real+other.real, imaginary+other.imaginary)
    //def +(other:Double) = new Complex(real+other,imaginary) // Not needed now
    def ==(other:Complex) = real==other.real && imaginary==other.imaginary
    override def toString: String = s"$real + ${imaginary}i"
}

object Complex {
    implicit def intToComplex(real: Int): Complex = doubleToComplex(real.toDouble)
    implicit def doubleToComplex(real: Double): Complex = Complex(real, 0)

    implicit def apply(real: Double, imaginary: Double): Complex = new Complex(real, imaginary)
    implicit def apply(tuple: (Double, Double)): Complex = Complex(tuple._1, tuple._2)

    def main(args: Array[String]) {
        val c1 = Complex(1, 2)
        println(s"c1: $c1")

        val c2: Complex = (3.4, 4.2) // Implicitly convert a 2-tuple
        println(s"c2: $c2")

        val c3 = 2 + c1
        println(s"c3: $c3")

        val c4 = c1 + 2 // The 2 is implicitly converted then complex addition is used
        println(s"c4: $c4")
    }
}

Some notes:

  • tested under 2.10.3. Depending on your version you might need the import at the top of the file.
  • once you have implicit conversions, you don't actually need the original + method which takes an Int input (hence I commented it out). Example c4 proves this.
  • consider making your class generic - that is instead of using Double, just use some numerical type which is broad enough for your needs. The concept of a complex number is actually completely decoupled from the field/ring/group it extends. For example, if your underlying type has multiplication and addition, you can define multiplication and addition for complex numbers too.
  • I added an implicit conversion for the Tuple2[Double, Double] class - just because it seemed cool. Example c2 demonstrates it.
  • similarly I added apply methods. Consider adding unapply methods to make it more case friendly.
  • I renamed your constructor arguments simply to make the toString method less confusing (so you don't have ${i}i)
  • (side track) you're == method is comparing type Double, not Int, so you'll get all the usual frustration and unexpected behaviour with floating point comparisons
rmin
  • 1,018
  • 1
  • 9
  • 18
  • Thanks, that's really helpful and shows some cool ways to use implicit. They're a nicer way of doing this than I'd hoped for. Just 2 questions, does the implicit do anything on the 2 argument function, and is the intToComplex necessary, I tried testing it and it seems to convert it through Double successfully. – Tesseract May 08 '14 at 18:42
  • @Tesseract, with your first question, can you clarify what you mean by the "2 argument function"? If you mean the apply method taking a `Tuple2`, that was just an extra thing I threw in. Regarding your second question, you're right - the `intToComplex` isn't actually necessary. That surprised me because I thought that implicit conversions make at most 1 implicit jump (e.g. from `Double` to `Complex`) but weren't able to jump implicitly fron `Int` to `Double` to `Complex`. – rmin May 10 '14 at 02:17
  • I was referring to the other `apply` method `implicit def apply(real: Double, imaginary: Double): Complex` I can't see how the implicit in that line does anything. – Tesseract May 10 '14 at 16:07
  • @Tesseract - you need the `implicit` to make example `c2` work. Good luck! – rmin May 13 '14 at 22:27
  • I thought that was done by `apply(tuple: (Double, Double))` rather than `apply(real: Double, imaginary: Double)`. – Tesseract May 14 '14 at 12:30
0

From section 6.2.3 of Scala Reference

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.

So the closest you can get is 2 +: c

Gangstead
  • 4,152
  • 22
  • 35