Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why these implicit conversions resulted in looping code

Tags:

scala

implicit

Consider following code in Scala:

object Test {
  class A {}

  class B extends A {}

  class AI extends A {
    def sayHello: String = "Hello from AI"
  }

  implicit def AtoAI(a: A): AI = a

  class BI extends B {
    def sayHello: String = "Hello from BI"
  }

  implicit def BtoBI(b: B): BI = b

  def main(args: Array[String]) {
    val a = new A
    println(a.sayHello)

    val b = new B
    println(b.sayHello)
  }
}

The use of implicits results in looping code. In fact, disassemblying reveals, that generated conversion methods have only a goto 0 inside:

public Test$AI AtoAI(Test$A);
  Code:
   0:   goto    0

public Test$BI BtoBI(Test$B);
  Code:
   0:   goto    0

What causes that behaviour? I understand, that a hierarchy of classes here is a suspect, but implicit conversion should be applied only once.

I use Scala 2.9.1

like image 347
Rafał Rawicki Avatar asked Jan 13 '12 10:01

Rafał Rawicki


2 Answers

Not nice but I would definitely not call it a bug.

It boils down to

class A

class B

implicit def aToB(a: A) : B = a

There is no need for the two sides of the conversion to be related in any way. The implicit is just the same thing as writing

implicit def aToB(a: A): B = aToB(a)

because the compiler inserts the aToB call in order to convert the result a to the required return type B.

The goto 0 implementation is just a tail call optimization. The compiler might probably gives a warning when it generates a method that starts this way.

Maybe there could be a rule that implicit methods are not available as implicits inside their own body. But it does not always create an infinite loop

implicit def listAToListB(l: list[A] = l match {
  case Nil => Nil
  case x:xs => toB(x) :: xs // equivalent to toB(x) :: listAToList[B](xs)
}

(ok this is just a map(toB)). Anyway the same could happen with two mutually recursive implicits. In my opinion, it's not worthwhile to tweak the spec just to avoid some possibility to write an infinite, do nothing loop, among many others. But a warning when such a loop is detected, irrespective of implicits, would be nice.

like image 126
Didier Dupont Avatar answered Sep 29 '22 03:09

Didier Dupont


I cannot understand why the code compiles at all. I verified that it does compile. Wouldn't you have to downcast inside the implicit conversion method?

Didier points out, that the implicit conversion gets applied recursively, which means the code can compile without a downcast.

The following code adds the downcasts (which should not change the runtime behavior). It fails at runtime with the failing casts. So this looks like a compiler bug to me. As Didier says, the explicit downcast prevents the recursive application of the implicit conversion here.

object Test {
  class A {}

  class B extends A {}

  class AI extends A {
    def sayHello: String = "Hello from AI"
  }

  implicit def AtoAI(a: A): AI = a.asInstanceOf[AI]

  class BI extends B {
    def sayHello: String = "Hello from BI"
  }

  implicit def BtoBI(b: B): BI = b.asInstanceOf[BI]

  def main(args: Array[String]) {
    val a = new A
    println(a.sayHello)

    val b = new B
    println(b.sayHello)
  }
}

Finishing by answering the question: you call a method of AI on an object of class A that doesn't have this method. Obviously this can't work. What happens is unspecified; in your case it was an infinite loop.

like image 38
ziggystar Avatar answered Sep 29 '22 04:09

ziggystar