General musings on programming languages, and Java.

Thursday, June 26, 2008

Programming in a natural order

Whenever I'm doing the same thing over and over, a little background thread in my brain starts to wonder if there's a better way. Sometimes I tell it to shut up and let me code, other times I listen. In this case it took me a long time to listen, so it must have been pretty persistent. The first time I noticed it was while writing some lisp. Here goes the usual overly trivial example:

I write (+ 3 4) then realise I want to multiply the result by 5. I have to go back to the start of the expression and change it to (* (+ 3 4) 5). I could easily blame lisp's prefix syntax and maybe define some reader macro so I can write something like (+ 3 4) >> (* 5). But that would solve one case and not really fix the overall problem. Here's the obligatory less trivial example:

I write (+ 3 x) then realise that x needs to be a lambda parameter, so I go back and write (lambda (x) (+ 3 x)). I think it's difficult to make the above >> reader macro work with this.
(+ 3 x) >> (lambda (x)) might work, but it's starting to get hard to read. It doesn't seem as natural as it did for the previous case.

But, as I said, it took me a long time to take notice of this background thread, and in fact when I did I was writing Scala, not lisp. When in lisp I just wrote the code anyway, and it didn't harm me noticably. I never wrote the above reader macro, and as I've never written one, chances are it's unwritable anyway.

The case when writing Scala was where I was looking at an expression, then realised that one of its inputs would be a collection, not a single value. A closer-to-reality example - you have a Client, and a Client has a manager, the contact there that you speak with. So you have:
def sendSpam(client: Client) = emailer.send(marketingTripe, client.manager.emailAddress). Then later you change Client.manager to Client.managers, and change its type from Manager to Iterable[Manager]. Now the emailer protests (at compile time), so you have some options:

1. Change the emailer to make it accept an Iterable[Client]. This is actually quite reasonable, and what I did in the real code this mimics.

2. Change the code to:
client.managers foreach (manager => emailer.send(marketingTripe, manager.emailAddress)).

3. Listen to the background thread in your brain, and blog about an automatic transform from the original version to option 2. Today I'm choosing option 3, as you can tell. To avoid ambiguity, I'll introduce some syntax to put around 'managers':
emailer.send(marketingTripe, ^(managers).emailAddress) will be transformed to the code from option 2 above (though perhaps without a real variable name).

At this point, you might be seriously glad that I am not a committer on the compiler for the language that you use every day. But I think the above might actually be a good idea, and might even be a better syntax for a map with a lambda than what we're used to. The reasoning is: it's preferable to write code in a natural order, rather than the order that the language forces you to.

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.

Blog archive

About Me

A salsa dancing, DJing programmer from Manchester, England.