Sunday, April 26, 2009

Some Java Fun becoming a major pain

Ich hab mal wieder mit GWT rumgespielt und etwas Java programmiert. Da ich ohne map, fold und filter nicht mehr auskomme und Java ja endlich Generics hat, hab ich mir eine kleine Fun Library geschrieben, damit ich z.B. sowas machen kann...

Set<String> strSet = Fun.mapS(intSet, new Fun<String,Integer>() {
  public Integer eval(String s) { return Integer.parseInt(s); }
});

Das funktioniert auch ganz gut, auch wenn die Syntax alles andere als angenehm ist. Ich hab also eine abstrakte Klasse

abstract class Fun<A,B> {
  public abstract B eval(A x);

mit Hilfsfunktionen wie

public static <A, B> Set<B> mapS(Collection<A> src, Fun<A,B> fun) {
  // ...
}

Wenn man eine Mapping-Funktion mehrfach braucht muss man sie auch nicht immer inline-definierien, sondern kann eine Konstante draus machen, also z.B. im Beispiel oben

Fun<String,Integer> STR_TO_INT = new Fun<String,Integer>() {
  public Integer eval(String s) { return Integer.parseInt(s); }
};

und dann braucht man nur noch

Set<String> strSet = Fun.mapS(intSet, STR_TO_INT)

schreiben. Alles wunderbar... nur dann brauchte ich eine Funktion um aus Map.Entrys die erste Komponente zu extrahieren (map.entrySet() ging in dem Fall nicht). Also

Fun<Map.Entry<String, Integer>, Integer> ENTRYKEY =
  new Fun<Map.Entry<String, Integer>, Integer>() {
    public Integer eval(Map.Entry<String,Integer> e) {
      return e.getKey();
    }
  };

und das funktioniert auch noch gut. Aber dann brauchte ich die Funktion für andere Typparameter. Na, kein Problem, kann man doch sicher generalisieren:

public static <A,B> Fun<Entry<A,B>, A> ENTRY_KEY_1 = new Fun<Entry<A, B>, A>() {
  @Override
  public A eval(Entry<A, B> x) {
    return x.getKey();
  }

Nur leider kompiliert das nicht. Polymorphe Werte scheint Java nicht zu unterstützen. (Oder hab ich's nur falsch gemacht?!) Okay, also eine Dummy-Funktion....

public static <A> Fun<Entry<A>, A> ENTRY_KEY_2() {
  return new Fun<Entry<A>, A>() {
    @Override
      public A eval(Entry<A> x) {
      return x.getKey();
    }
  };
};

Die kann man definieren, aber leider nicht aufrufen!

Pain.java:38: <A>mapS(java.util.Collection<A>,Fun<A>) in Fun cannot
be applied to
(java.util.Set<java.util.Map.Entry>,Fun<java.util.Map.Entry,java.lang.Object>)

Wahrscheinlich weil nur anhand der Parameter die richtige Belegung ausgewählt werden kann. Also muss man einen Phantom-Parameter einführen...

public static <A> Fun<Entry<A>, A> ENTRY_KEY_3(Entry<A> phantom) {
  return new Fun<Entry<A>, A>() {
    @Override
      public A eval(Entry<A> x) {
      return x.getKey();
    }
  };
};

und dann beim Aufruf

Set keys = Fun.mapS(map.entrySet(), ENTRY_KEY_3((Entry) null));

Oh.... das tut weh!

Weiss jemand wie das mit Generics in C++ und C# ist? In Scala geht das sicher, oder?

Schade, dass GWT Java SourceCode nach JavaScript kompiliert und nicht, wie ich dachte, Java ByteCode. Damit man mit GWT in den Genuss von Scala kommt muss man wahrscheinlich erst Scala nach ByteCode komilieren und dann mit einem Dekomiler aus dem ByteCode wieder JavaCode machen und den dann mit GWT kompilieren. Oder man hackt gleich getyptes JavaScript in Haskell aber Dmitry Golubovsky ist nicht Google und Version 0.1 ist wohl eher ein Proof of concept. Wahrscheinlich sollte ich halt doch einfach schön imperativ in Java programmieren und Generics nur verwenden um ein paar Casts zu sparen. :-/

Autor: David Leuschner