Monthly Archives: July 2009

Scala Implicit Conversion

I’ve just finished Programming in Scala by Martin Odersky. So in next few post I will describe my thoughts about Scala. Shortly I recommend you to get know something about this language. I quote:

"If I were to pick a language to use today other than Java, it would be Scala" by James Gosling

"I can honestly say if someone had shown me the Programming in Scala book by by Martin Odersky, Lex Spoon & Bill Venners back in 2003 I’d probably have never created Groovy." by James Strachan.

Interesting, isn’t it?

Scala has a lot of futures we do not have in java, one thing I like about scala is that most of new features  are de facto libraries, this libraries simply extend language (the name scala comes from scalable language). 

In this post I will focus on implicit conversions. It is similar to java cast, which are explicit, but of course implicit conversion are more powerful feature, which is used in scala to extend existing libraries easier.

Implicit conversion is a function which takes one type as parameter and return other type. Example in java space will be String.valueOf(int i) which takes int type and return String object. So where is the fun in this :)

The fun comes from fact that this functions are applied implicitly by compiler with full type check. To do this compiler require to follow some rules:

  1. Implicit conversion must be in scope as single identifier, or be associated with the source or target type.

    /* put conversion into scope */
    import Preamble._  /* many libraries import in this way library implicit conversions"
    /* or  as companion object */
    class Person { ... }
    object Person {
    /* implicit conversions goes here for Person type */
    }
    
  2. One conversion at the time, so there isn’t implicit conversion chaining.
  3. There may be only one way to apply implicit conversion, otherwise compiler rise error.
  4. And the last is of course that explicit has priority.

Writing implicit conversion is very easy, we just put implicit keyword before method, and we are done. The name of the implicit conversion depends on developer, I mostly saw type2target convention as example some implicit conversion from Predef: any2ArrowAssoc, int2double etc. Of course there are also typeToTarget and typeWrapper as well.

Want to assign double to int just wirte this implicit and it works.

scala>  implicit def double2int(d:Double): Int = d.toInt
scala> val i : Int = 3.5

Ok so what we can do with implicit conversions. Implicit conversion is tried in such situations:

  1. As expected type (extended cast alike).
  2. Receiver conversion.
  3. Also as implicit parameters

First rule applies in many cases, as behind the scene action. For example assume this code:

val d : Double = 3 /* 3 is Integer */
/* this code is translated by compiler into val d: Double - Predef.int2double(3) */

It works perfectly because in fact compiler is using Predef.int2double implicit conversion. We can do in such way proper object translation to use it with current libraries.

Map( "first"->"Test", "second"->"Code")

It looks like a special syntax but in fact it is just implicit conversion in action. The code is translated into:

  Map.apply(any2ArrowAssoc("first").->("Test"), any2ArrowAssoc("second").->("Code"))

I omit generics and Predef package to make this code more readable, as we see this "syntax" in fact use any2ArrowAssoc implicit conversion defined in Predef.

As all we know with great power, comes great responsibility. It may be hard to know what is going on with my code, when implicit conversions are overused. So key fact is that always we can use implicit conversion as normal method and call it explicit. Second scala compiler has -Xprint:typer option parameter which can be use to show scala code after applying implicit conversions. I was using it to show the code of Map pseudo-syntax. Example looks like this:

/* scala code Test.scala */
object Test extends Application {
  Map( "first"->"Test", "second"->"Code")
}

Console output

pedro$ scalac -Xprint:typer Test.scala 
[[syntax trees at end of typer]]// Scala source: Test.scala
package  {
  final object Test extends java.lang.Object with Application with ScalaObject {
    def this(): object Test = {
      Test.super.this();
      ()
    };
    scala.this.Predef.Map.apply[java.lang.String, java.lang.String](
scala.this.Predef.any2ArrowAssoc[java.lang.String]("first").->[java.lang.String]("Test"), 
scala.this.Predef.any2ArrowAssoc[java.lang.String]("second").->[java.lang.String]("Code"))
  }
}

There are also implicit parameter which also use implict to add additional parameter to the method calls. Implicit conversion is quite simple but very powerful feature, isn’t it? What do you think about it? In my opinion is quite useful.

Maven 2.2.0 Released

New version of maven was released. One of the benefit is that there are many bug fixes and some regression from 2.1.0 was fixed.

The main issue when upgrading is that new maven requires Java 1.5 or later! If you still have system which must be build for Java 1.4 you’ve got trouble to overcome. There is guide how you can configure your pom to use ensure that you do not use API from later version t han suggested.

This is achieved by using animal-sniffer. Animal-sniffer is project which goal is to provide tools for developers which must use many different Java versions. For example I use java 1.4, java 5 and java 6 in my daily work.

Currently there are maven plugin and command line tool. Command line help us to find which class is compiled by which java version, it may help to spot libraries or modules compiled by higher version than our project.

Most helpful is maven plugin which can be configure in such way that it rise build error if we use API from higher java version. Assume the code:

package info.pietrowski;

public class Main {
    public static void main(String[] args) {
        BigDecimal d = new BigDecimal(10); // this constructor is since 1.5
        System.out.println(d);
    }
}

Now we configure maven to target for 1.4 java platform, but JAVA_HOME points to java 5 or higher

...

  maven-compiler-plugin
  
    1.4
    1.4
  

...

Running this program with different java version drive to the output

$ jdk1.6.0_13/bin/java -classpath test.jar info.pietrowski.Main
10
$ j2sdk1.4.2_16/bin/java -classpath test.jar info.pietrowski.Main
Exception in thread "main" java.lang.NoSuchMethodError: java.math.BigDecimal.(I)V
        at info.pietrowski.Main.main(Main.java:10)

The problem is that compiler choose BigDecimal constructor based on rt.jar, and despite produce bytecode 1.4 compatible, can run it because rt.jar in java 1.4 do not have this constructor.

The rescue for this problem is animal-sniffer maven plugin which we can configure like this:

...
  
    org.jvnet
    animal-sniffer
      
        
          animal-sniffer
          compile
            
              check
            
            
              
                org.jvnet.animal-sniffer
                java${jdk.level}
                1.0
              
            
          
        
      
        
          org.jvnet.animal-sniffer
          java${jdk.level}
          1.0
          sig
        
     
   
...

Where jdk.level is property for target java version. Also this two resource has some configuration guidance:
Signature Checker guideline and Guide to building 1.4 project.

Currently they have signatures for

  • java1.3
  • java1.4
  • java1.5
  • java1.6

And hopefully you do not have to use older java than 1.3.

After this change pom like suggested and try to build by java higher than 1.5 we get this error message:

[INFO] [animal-sniffer:check {execution: animal-sniffer}]
[INFO] Checking unresolved references to org.jvnet.animal-sniffer:java1.4:1.0
[ERROR] Undefined reference: java/math/BigDecimal.(I)V in ...\target\classes\info\pietrowski\Main.class
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Signature errors found. Verify them and put @IgnoreJRERequirement on them.

Thanks to Marcin who test with me this configuration and finally put this into our organization parent pom for all our developers.