Hacking in the JVM: Return type polymorphism in Scala

The first thing that springs to mind when thinking about method polymorphism is the use case of having an identically named method accepting different argument types.

Let’s observe the following trivial example:

    def mul(x: Int, y: Int) = x * y
    def mul(x: String, y: String) = x + "*" + y

Both Java and Scala support this type of polymorphism; we’ll use this example as an entry point into our hacking session:

    scala> mul(2, 3)
    res0: Int = 6
 
    scala> mul("2", "3")
    res1: String = 2*3

So, what is return type polymorphism (return type overloading)?
Basically this means creation of methods which differ only by their return types. Here is a simple example:

    def div(x: Int, y: Int): Int = x / y
    def div(x: Int, y: Int): String = x + "/" + y
    def div(x: Int, y: Int): BigDecimal = BigDecimal(x) / y

A keen observer would rightfully comment that this does not compile, not with vanilla Scala 2.10.x at least. Let’s give it a prod.

ENTER JASMIN!

Jasmin is an assembler – a very low level programming language for the JVM. It’s about as low as you could comfortably get unless you want to write class files with a hex editor.

Let’s implement the upper three methods in pure Jasmin … <rolls up sleeves>

    .class public io.jvm.poly.Tester
    .super java/lang/Object
 
    ; local_0 / local_1
    .method public static div(II)I
      .limit locals 2
      .limit stack 2
 
      iload_0
      iload_1
      idiv
      ireturn
    .end method
 
    ; local_0 + "/" + local_1
    .method public static div(II)Ljava/lang/String;
      .limit locals 2
      .limit stack 2
 
      iload_0
      invokestatic java/lang/String.valueOf(I)Ljava/lang/String;
      ldc "/"
      invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/String;
      iload_1
      invokestatic java/lang/String.valueOf(I)Ljava/lang/String;
      invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/String;
      areturn
    .end method
 
    ; BigDecimal(local_0) / BigDecimal(local_1)
    .method public static div(II)Lscala/math/BigDecimal;
      .limit locals 2
      .limit stack 2
 
      iload_0
      invokestatic scala/math/BigDecimal.int2bigDecimal(I)Lscala/math/BigDecimal;
      iload_1
      invokestatic scala/math/BigDecimal.int2bigDecimal(I)Lscala/math/BigDecimal;
      invokevirtual scala/math/BigDecimal.$div(Lscala/math/BigDecimal;)Lscala/math/BigDecimal;
      areturn
    .end method

Download the Jasmin source code “io.jvm.poly.Tester.j”
Download the latest Jasmin assembler from Sourceforge

In order to compile this class yourself using Jasmin, simply run the jasmin.jar against the source code:
“java -jar jasmin.jar io.jvm.poly.Tester.j”

All right – now we have a separate class file containing the overloaded methods.
Since they all have the same name, we can import them all at once via the following directive:

    scala> import io.jvm.poly.Tester.div
    import io.jvm.poly.Tester.div
 
    scala> div(7, 2): Int
    res0: Int = 3
 
    scala> div(7, 2): String
    res1: String = 7/2
 
    scala> div(7, 2): BigDecimal
    res2: BigDecimal = 3.5

And there you have it – although it’s impossible to compile such a class directly from Scala, once it’s compiled and added to the classpath it is very easy to invoke it.

Of course, this doesn’t mean that you really need to start programming in Jasmin. If you desire for such functionality, simply create a proxy method in Jasmin which invokes your original Java/Scala/JProlog or whatever:

Rnd.scala

    package io.jvm.poly
 
    object Rnd {
      def makeInt() = scala.util.Random.nextInt
 
      def makeString() = Array.fill(10)(scala.util.Random.nextPrintableChar).mkString
    }

io.jvm.poly.RndProxy.j

    .class public io.jvm.poly.RndProxy
    .super java/lang/Object
 
    .method public static makeRnd()I
      .limit locals 0
      .limit stack 1
 
      invokestatic io/jvm/poly/Rnd.makeInt()I
      ireturn
    .end method
 
    .method public static makeRnd()Ljava/lang/String;
      .limit locals 0
      .limit stack 1
 
      invokestatic io/jvm/poly/Rnd.makeString()Ljava/lang/String;
      areturn
    .end method

You will now be able to invoke the Rnd object methods by using the Jasmin compiled polymorphic method:

    scala> io.jvm.poly.RndProxy.makeRnd: Int
    res0: Int = 1958204046
 
    scala> io.jvm.poly.RndProxy.makeRnd: String
    res1: String = qwOSsm6D9

In essence, return type overloading is a bit of a gimmick. The only prevalent use case where return type
overloading is consistently used is in Java obfuscators/optimizers (Proguard) where they make classfiles
smaller and more confusing by having a dozen different methods all named a().

One example of such overloading which actually made it into production can be seen here:
https://github.com/melezov/pgscala/converters-scala/org/pgscala/converters/PGNullableConverter.j

    scala> import org.pgscala.converters.PGNullableConverter
    import org.pgscala.converters.PGNullableConverter
 
    scala> PGNullableConverter.fromPGString("t"): Boolean
    res0: Boolean = true
 
    scala> PGNullableConverter.fromPGString("3"): Double
    res1: Double = 3.0
 
    scala> PGNullableConverter.fromPGString(""""a"=>"b", "c"=>"d""""): Map[String, String]
    res2: Map[String,String] = Map(a -> b, c -> d)

Let’s say that you want to create a User case class …

  case class User(name: String, age: Int, born: LocalDate)

… from a ResultSet variable rs …

  { rs => User(rs.getString("name"), rs.getInt("age"), rs.getLocalDate("born")) }

By using return type overloading you would be able to make a tiny wrapper around the original ResultSet and write this instead:

  { rs => User(rs.get("name"), rs.get("age"), rs.get("born")) }

Basically this allows you to perform easy model migrations:

-  case class User(name: String, age: Int, born: LocalDate)
+  case class User(name: String, age: Int, born: DateTime)

Without changing the actual code – the compiler is going to inject the proper method by looking at the required return type:

  { rs => User(rs.get("name"), rs.get("age"), rs.get("born")) }

If you feel clever, you may also try overloading the apply method on your class 🙂

There are a couple other examples I could list, but this kind of an approach is losing value recently as Scala macros are stepping out of their experimental phase and are becoming more relevant.

2 thoughts on “Hacking in the JVM: Return type polymorphism in Scala

  1. Simon

    > And there you have it – although it’s impossible to compile such a class directly from Scala, once it’s compiled and added to the classpath it is very easy to invoke it.

    That’s not completely correct:


    package scala
    object Foo {
      def bar: Int = ???
      @scala.annotation.bridge
      def bar: Long = ???
    }

    1. Marko Elezović Post author

      Heya Simon!

      I read the discussion on scala-internals, and I must admit I had no clue @bridge methods even existed.

      Although, it’s currently not usable as-is because it’s defined with a private[scala] scope, perhaps in the future we could (ab)use it; according to the comment by D. C. Sobral:

      “Bridge was deemed too new with too little time to mature to be made public. Perhaps it would be interesting to make it public on trunk, so that people could actually use it to provide feedback.”

Leave a Reply

Your email address will not be published. Required fields are marked *