Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the Scala compiler handle concrete trait methods?

Tags:

java

scala

traits

If I have the following Scala class:

abstract class MyOrdered extends Ordered[MyOrdered] {
    def id: Int
    def compare(that : MyOrdered) : Int =
        if (that==null) 1 else (id-that.id)
}

Then I only need to define the id method in Scala to get a concrete class. But if I try to extend it in Java, the compiler says that all the concrete methods of Ordered are missing. So, does that mean that the Scala compiler is only putting the implementation of the concrete methods of Ordered in concrete Scala classes?

This seems very wasteful, because I could have dozens of concrete classes implementing MyOrdered, and they would all get a copy of the same code, when in fact it would be enough to just put it directly in the base class MyOrdered. Also, this makes it very difficult to create a Java-friendly Scala API. Is there any way to force the Scala compiler to put the method definitions where it should have done so anyway, apart from making the class concrete by using dummy method implementations?

Even funnier is declaring a concrete method final in a Scala trait. In that case, it is still not implemented in an abstract Scala class that extends the trait, but it cannot be implemented in a Java class that extends the abstract Scala class because it is flagged as final. This is definitely a compiler bug. Final abstract methods make no sense, even if they are legal in the JVM, apparently.

like image 221
Sebastien Diot Avatar asked Aug 14 '11 13:08

Sebastien Diot


People also ask

Can Scala traits have methods?

In scala, trait is a collection of abstract and non-abstract methods. You can create trait that can have all abstract methods or some abstract and some non-abstract methods. A variable that is declared either by using val or var keyword in a trait get internally implemented in the class that implements the trait.

What are traits used for in Scala?

Traits are used to define object types by specifying the signature of the supported methods. Scala also allows traits to be partially implemented but traits may not have constructor parameters. A trait definition looks just like a class definition except that it uses the keyword trait.

Can traits be instantiated in Scala?

Unlike a class, Scala traits cannot be instantiated and have no arguments or parameters.

What is the difference between Java interface and Scala trait?

Traits in Scala have a lot of similarities with interfaces in Java, but a trait is more powerful than an interface because it allows developers to implement members within it. The trait is a combination of abstract and non-abstract methods. Trait can not be instantiated, thus it has no parameters.


Video Answer


1 Answers

Scala 2.9.1.RC1

Let me introduce you to our friend :javap in the REPL, which can be useful for diagnosing errors. First, we define the class,

scala> abstract class MyOrdered extends Ordered[MyOrdered] {
     |     def id: Int
     |     def compare(that : MyOrdered) : Int =
     |         if (that==null) 1 else (id-that.id)
     | }
defined class MyOrdered

And then ask to see the JVM bytecode,

scala> :javap -v MyOrdered
Compiled from "<console>"
public abstract class MyOrdered extends java.lang.Object implements scala.math.Ordered,scala.ScalaObject

...
** I'm skipping lots of things here: $less, $lessEq, ... **
...

public boolean $greater(java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   invokestatic    #19; //Method scala/math/Ordered$class.$greater:(Lscala/math/Ordered;Ljava/lang/Object;)Z
   5:   ireturn
  LineNumberTable: 
   line 7: 0

...

public abstract int id();

public int compare(MyOrdered);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_1
   1:   ifnonnull   8
   4:   iconst_1
   5:   goto    17
   8:   aload_0
   9:   invokevirtual   #38; //Method id:()I
   12:  aload_1
   13:  invokevirtual   #38; //Method id:()I
   16:  isub
   17:  ireturn
  LineNumberTable: 
   line 10: 0

 ...

We see that scalac actually generates methods in MyOrdered corresponding to those concrete ones in trait Ordered. For example, the > method gets translated to $greater and basically just calls scala/math/Ordered$class.$greater. If we like, we can now look up the bytecode for concrete trait definitions,

scala> :javap -v scala.math.Ordered$class
Compiled from "Ordered.scala"
public abstract class scala.math.Ordered$class extends java.lang.Object
...
public static boolean $greater(scala.math.Ordered, java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   invokeinterface #12,  2; //InterfaceMethod scala/math/Ordered.compare:(Ljava/lang/Object;)I
   7:   iconst_0
   8:   if_icmple   15
   11:  iconst_1
   12:  goto    16
   15:  iconst_0
   16:  ireturn
  LineNumberTable: 
   line 46: 0
...

Finally, let's test your hypothesis that a subclass M of MyOrdered gets a full copy of all the methods

scala> class M extends MyOrdered { def id = 2 }
defined class M

scala> :javap -v M
Compiled from "<console>"
public class M extends MyOrdered implements scala.ScalaObject
....
** No extra methods besides id **
....

Nope, it looks like there's no code duplication here.

To conclude,

  • Scalac does some magic with traits with concrete methods, so don't try to inherit from them in Java. Abstract classes should be OK.

  • The JVM doesn't natively support symbolic method names, Scala singleton objects, nor traits with concrete methods, so the Scala compiler needs to do some translation, and uses the reserved symbol $.

If you're still having problems with Java interop, hopefully :javap will help you in diagnosing the specific problem.

like image 97
Kipton Barros Avatar answered Oct 11 '22 06:10

Kipton Barros