How They Find Lost Luggage
For a prototype at work I needed to modify the way the standard Maven 2 compiler plugin works. Since Maven 2 plugins are pure Java it seemed the nicest way would be to make a new plugin, put the original compiler plugin as a dependency, extend the necessary classes with my own logic and bob's my uncle.
The Maven compiler plugin classes are indeed extendable and the modifications I required would maybe consist of 20 lines of code. However, the compiler plugin is not just code; it has a configuration API in the form of a bunch of properties that can be set to different values as well as properties that have to be injected with standard Maven components (archive manager, the project, etc).
Maven plugins define configurable properties in the form of annotations, not java annotations but xdoclet-like annotations that are inside the Javadoc comments. Oops. By inheriting from the compiler plugin in binary form (just including the jar on the classpath) that metadata is lost since comments are not retained in the classfiles. What Maven normally does when building a plugin (with Maven of course) is to extract all that comment based metadata, convert it into an xml file and package that together with your binary plugin.
Some extensive Googling turned up just the solution for this: the good folk at OPS4J have a Maven plugin called the maven-inherit-plugin that you can use in building your own plugin and that allows you to extend another plugin in binary form (yes, that's a plugin threesome). It merges the metadata of the dependent plugin into your own at compile time.
Recommended.
Now and again when working with text files I run into the need of having to concatenate a bunch of strings with some separator in between (say a comma). In languages such as Python you typically have a join() method on String that allows you to do this: the string is the separator and the parameter to join is a list or iterable of some sort.
Scala doesn't seem to have such a method on String, or at least I couldn't find one.
The bare metal Java way to solve this tiny problem - a for loop with an index where you can check whether you reached the end or not - doesn't seem very Scala-like at all.
But there is an alternative that looks better: reduceLeft. This is one of those mysterious functional idioms that are sprinkled all over Scala and that seem apocryphal to the lowly imperative programmer (myself included!).
reduceLeft is a method on List that takes an anonymous function with 2 arguments (a lambda) as a parameter and then applies that function to the first element of the list and the second element, then to the result of that application and the third element, etcetera etcetera. This gives us a neat way to implement join in Scala:
val someList = List("foo", "bar", "baz", "qux")
val concatenated = someList.reduceLeft(_ + ", " + _)Now concatenated contains "foo, bar, baz, qux". Note that I used the shortcut syntax for the anonymous function, you can also write it out:
val concatenated = someList.reduceLeft( (x1: String, x2: String) => x1 + ", " + x2)
But why would you?
Update 1.12.2009: Or you could just use the mkString method on Iterable. Doh! I totally missed that.
As a newbie in Scala coming from a mostly Java background, I ran into a non-obvious problem.
Having some test code like this:
def foo() : Unit = { val bar = 5 }The Scala compiler would complain with: "error: block must end in result expression, not in definition".
I knew what the compiler meant: it was expecting a value or an expression that it could treat as the return value of the function but I did not understand why. I assumed that Unit was like void, surely there is no return value required?
The fix is obvious, instead of a val declaration like this, just have a statement that returns a value. Like say:
def foo() : Unit = { 5 }Unit is an actual return type and so the compiler expects something. The actual explanation required some Googling and a blog post by James Iry goes into quite some detail on the topic.
From a design point of view it is a bit of the functional nature of Scala peeking through: Java programmers are used to writing methods that just have a few side effects but don't return anything. In a pure functional context - no side effects - a function that doesn't return anything doesn't have to be executed and therefore has no reason to exist.
So, Java programmers, remember: void != Unit.
Arrays in Java are special, not in a good way, but in a nasty exceptional, non-standard way. Being the only datatype to be parametrized (or "generic") since before Generics were introduced in Java they also have the property of being covariant: you can use an Array of Strings when one of Objects is required for example, since String is a subtype of Object Java considers the Array of String to be a subtype of Array of Object.
In Scala however, generics are quite clean and arrays are not so special and therefore not covariant by definition. This is fine and both worlds can coexist in peace and tranquility.
Until you start calling Java code from Scala.
The example that bit me was a piece of trivial Swing code where I wanted to instantiate a JComboBox with an array of Strings:
val comboBox = new JComboBox(Array("foo", "bar"))Boom went the compiler: "error: overloaded method constructor JComboBox with alternatives (java.util.Vector[_])javax.swing.JComboBox <and> (Array[java.lang.Object])javax.swing.JComboBox <and> (javax.swing.ComboBoxModel)javax.swing.JComboBox cannot be applied to (Array[java.lang.String])"
Turns out that the JComboBox constructor takes an array of Objects (in order to be generic and allow any kind of object to be displayed in a combo box). The Scala compiler however does not "do what I want" and complains that it can't find a suitable constructor.
The fix is a simple as it is absolutely horrifyingly impossible to Google for, simply provide a generic type for your array that fits the Java API:
val comboBox = new JComboBox(Array[AnyRef]("foo", "bar"))Note the "AnyRef". Easy peasy, if you know what's up. Thanks to Vassil Dichev for helping out a noob.
As tradition demands it, and to test this new service. A small "Hello, world."