Ricky has posted 1 posts at DZone. View Full User Profile

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

06.25.2008
| 8596 views |
  • submit to reddit

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(); }

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:

Java:

public class Pair<T, U> 
{ ...
public <V> Pair<V, Pair<T, U>> prepend(V v)
{ return pair(v, this); } }

Scala:

implicit def Tuple2WithPrepend[T, U](tuple: (T, U)) = 
new { def prepend[V](v: V) = (v, tuple) }

C#:

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.

From http://rickyclarkson.blogspot.com

Published at DZone with permission of its author, Ricky Clarkson.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)