General musings on programming languages, and Java.

Wednesday, June 25, 2008

A Cross-Language Generics Trick - Java, Scala and C#

Given a Pair<T, U> type in Java, Scala or C#, such as Map.Entry, Tuple2 or KeyValuePair respectively, you can construct type-checked variadic heterogenous containers that you can write general methods to operate on.

Let's write a Pair interface for Java and C#:

interface Pair<T, U> {
 T _1();
 U _2(); }
For Scala we'll use Tuple2, which has _1 and _2 as well. You could use Map.Entry and KeyValuePair in Java and C# respectively, but they seem to have extra semantic information in their names. I know some readers will be crying out for more semantic information than _1 and _2, but I hope they bear with me a moment.

Eliding the implementation, one could have a line of code like the following easily:

Java: Pair<String, Integer> pair = Pairs.pair("hello", 5);
Scala: val pair=("hello", 5)
C#: val pair = Pairs.Pair("hello", 5);
I expect that's fine with most people. For C# you'd probably change 'pair' to 'Pair' for the method name. Then, to start introducing the trick:
Java: Pair<Double, Pair<String, Integer>> withDouble = Pairs.pair(3.0, pair);
Scala: val withDouble = (3.0, pair)
C#: var withDouble = Pairs.Pair(3.0, pair);
You can see that the type in the Java code starts to look a little messy; this is no accident. Explicit static typing makes us more likely to choose less expressive types. Anyway, we can add a method 'prepend' to the Pair type, which doesn't modify anything, but returns a new Pair consisting of a data item on the left and the original Pair on the right. So we get:
Java: Pair<Double, Pair<String, Integer>> pair = Pairs.pair("hello", 5).prepend(3.0);
Scala: val pair = Pairs.pair("hello", 5) prepend 3.0
C#: var pair = Pairs.Pair("hello", 5).Prepend(3.0);
So prepend must be an interesting method, because it looks like you can use it to add more type parameters to something. Clearly you can't, I'm just chaining Pairs, but it makes a nice effect. So far not very useful; I'll get to that. First let's implement prepend:
 public class Pair<T, U> { ...
  public <V> Pair<V, Pair<T, U>> prepend(V v) {
   return pair(v, this); } }
 implicit def Tuple2WithPrepend[T, U](tuple: (T, U)) = new {
  def prepend[V](v: V) = (v, tuple) }
 public class Pair<T, U> { ...
  public Pair<V, Pair<T, U>> Prepend<V>(V v) {
   return pair(v, this); } }
The nice part about this way of building up Pairs is that you can write methods to handle them instead of writing one per Pair arity. Specifically, you could gather up parameters for an immutable class then instantiate it in one go. In fact, that's what I do in a prototype for a JDBC wrapper. To wet the tastebuds (sorry, only Java for this one):
List<QuestionInfo> questions=select(conn).asString("question").asString("correct").asString("wrong").as(question).from("questions").toList();
The idea is that the above runs the SQL query: select question, correct, wrong from questions and constructs a QuestionInfo for each result, putting the result into a list.

The surprising thing is probably that there's no reflection or casting going on at all. Each asString (well, after the first one really) builds up more in a chain of generic types, then the .as(question) deconstructs them again. question is actually an F<Pair<String, Pair<String, String>>, QuestionInfo>, which means it's a function that takes 3 Strings and returns a QuestionInfo, roughly.

The above code comes from a working test case I published here.

It turns out that someone else had this idea way way way before I did, and made something professional out of it, though only some of that appears to be statically type-checked.

I hope that what I've showed here proves useful to you, and if you are my team leader and I pointed you at this page, remember that you saw it on the Internet, it's real, so you have to let me write it in our project.


James said...

Clever post - I'd really think this would do well on JavaLobby. If you're interested, send me a mail to james at dzone dot com and we can organise it.

Fatih Coşkun said...

Nice one, thanks! Is this similar to what LinQ in C# is doing, except that LinQ has more syntactic sugar.

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.