Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing numbers

Tags:

generics

scala

I'm quite new to Scala. I want to write several mathematical objects (Complex, Polynomial, etc.) which are closed under some operations (+, -, *) in a way that they can be used in generics and implicit casts can be used.

I seem to have solved the first bit.

trait GroupUnderAddition[T] {
  def + (t : T) : T
}

case class Real(d : Double) extends GroupUnderAddition[Real] {
  def + (r : Real) = Real(d + r.d)
}

case class Complex(re : Double, im : Double) extends GroupUnderAddition[Complex] {
  def + (c : Complex) = Complex(re + c.re, im + c.im)
}

object Test {
  implicit def real_to_complex(r : Real) = Complex(r.d, 0)

  def test[G <: GroupUnderAddition[G]](a : G, b : G) = a + b

  def main(args : Array[String]) {
    println(test(Real(5), Real(2)))
  }
}

Now, how can I write test() so that

test(Real(5), Complex(2, 1))

returns Complex(7, 1) ?

like image 588
Karolis Juodelė Avatar asked Feb 12 '12 10:02

Karolis Juodelė


2 Answers

The main idea is that all GroupUnderAddition are not compatible, so as you seem to be wanting to work with complex algebra, I would suggest to build a superclass including GoupUnderAddition. However it is not recommended to make it a case class (see warnings if you have a case class extending a case class)

trait GroupUnderAddition[T] {
  def + (t : T) : T
}

class ComplexAlgebra(_re:Double, _im:Double) extends(GroupUnderAddition[ComplexAlgebra]) {
  val re = _re
  val im = _im     
  def + (c : ComplexAlgebra) = new ComplexAlgebra(re + c.re, im + c.im)  
}

case class Real(d : Double) extends ComplexAlgebra(d, 0)

case class Complex(real : Double, imaginary : Double) extends ComplexAlgebra(real,imaginary)

object Test {

  def test(a : ComplexAlgebra, b : ComplexAlgebra) = a + b

  def main(args : Array[String]) {
    println(test(Real(5), Real(2)))
  }
}
like image 96
Christopher Chiche Avatar answered Oct 05 '22 13:10

Christopher Chiche


The problem is that the implicit def is not considered for argument conversion, unless you specify the method definition to do so.

Hence, if you have something like Real(5).foo and foo is only defined for complex, the implicit def will work for that.

If you have a method like: def foo(c : Complex) = ... you may not call it with foo(Real(5)) instead.

If you want to apply implicit conversion, you must specify your method such that its arguments may be converted. For the above foo method you could do it like this:

def foo[T](c : T)(implicit ct : T => Complex) = ...`

Then it is valid to call foo(Real(5)) and the conversion will be used.

Adapted to your specific problem, you could write the test method like this:

def test[G <: GroupUnderAddition[G],T](a : T, b : G)(implicit ag: T => G) = a + b

By specifying, that implicit conversions from T to G shall be taken into account, you allow the test method to now accept test(Real(5), Complex(2,1)).

However, it will not work the other way round yet. So you cannot yet call it with test(Complex(2,1), Real(5)), because there is no implicit conversion of the second argument.

A straightforward way to account for both conversion would be to write it like this:

def test[G <: GroupUnderAddition[G],T1, T2](a : T1, b : T2)(implicit ag: T1 => G, bg: T2 => G) = a + b

Unfortunately, the compiler somehow derives Any for G when calling this method like above. I don't know right now, how to resolve this problem and I posted this answer in the hope that someone else might fill in this last piece of the puzzle.

Given the above final definition you can at least call the method in either way, when specifying the full types:

println(test[Complex,Real,Complex](Real(5), Complex(2, 1)))
println(test[Complex,Complex,Real](Complex(2,1), Real(5)))
like image 41
Frank Avatar answered Oct 05 '22 13:10

Frank