Alles Immutable: Wie geht das?

Ein radikaler Ansatz, um Multithreading zu vereinfachen, ist es „alles“ immutable zu machen. Man meint, dass das in Java schon recht gut der Fall ist, sind doch Objekte von grundlegenden Klassen wie String, Long, Integer, BigInteger, BigDecimal u.s.w. immutable. Date ist ein bisschen ein Spezialfall, da es fast immer verwendet wird, als wäre es immutable, was aber in Wirklichkeit nicht stimmt.

Aber die Collection-Klassen sind natürlich a priori fast alle mutable. Müssen sie ja sein, weil es z.B. keine vernünftige Schreibweise für eine Map gibt und man sie deshalb sukzessive aufbauen muss. Zwar gibt es die unmodifiable-Wrapper in Collections, aber wenn man Zugriff auf die eingebaute Collection hat, kann man diese immer noch ändern, was zu Überraschungen führen kann.

Die üblichen Collections in Clojure funktionieren anders. Sie sind wirklich immutable. Wenn man also so etwas ähnliches wie ein map.put(k, v) oder map[k]=v in Clojure macht, wird map selber nicht verändert, sondern es wird eine Kopie zurückgegeben, die zusätzlich das neue Paar enthält.

Hier ein Beispiel:

user=> (def x (hash-map 3 4 5 6))
#'user/x
user=> x
{3 4, 5 6}
user=> (def y (assoc x 7 8))
#'user/y
user=> y
{3 4, 5 6, 7 8}
user=> x
{3 4, 5 6}

Man kann sich vorstellen, dass da tatsächlich immer Kopien gemacht werden. Dann würde Clojure natürlich für größere Programme exorbitant Speicher verbrauchen und wäre auch sehr langsam.

Man muss sich eher vorstellen, dass ausgenutzt wird, dass auch die Eingaben immutable sind und dass geschickte interne Datenstrukturen sicherstellen, dass man das Verhalten bekommt, als wäre es kopiert worden, aber intern Optimierungen stattfinden, die mehrfach benutzte Daten gemeinsam nutzen.

Share Button

GNU-Emacs und Unicode

Heute sollte man Text-Dateien bevorzugt in Unicode erstellen und speichern. Natürlich braucht man nur englische Texte, deshalb reicht ISO-646 (ASCII) aus, aber ein paar Umlaute kommen doch noch rein, allein wegen Eigennamen und so kann man ISO-8859-1 oder ISO-8859-15 nehmen und hat die Umlaute auch dabei. Praktisch mit demselben Aufwand kann man stattdessen UTF-8 verwenden. Das erlaubt es, die Umlaute zu haben, aber auch alle anderen Unicode-Zeichen. Und die seltenen Umlaute sind zwei Bytes statt einem groß, was in aller Regel vernachlässigbar ist. Eine andere Frage ist es aber, wenn man z.B. Russisch, Griechisch, Japanisch oder Chinesisch schreibt. utf-8 führt dazu, dass die griechischen und russischen Buchstaben mindestens zwei Bytes brauchen und die chinesischen und japanischen noch mehr. Bei diesen Sprachen ist zugegebenermaßen der Anreiz, eine Codierung zu verwenden, die auf diese Sprache zugeschnitten ist, größer, weil man so die Textdateien um Faktor 1.5 oder 2 kleiner machen kann. Weiß jemand, wie das heute praktiziert wird? In welcher Codierung speichert man üblicherweise Textdateien bei Sprache, die nicht auf lateinischen Buchstaben basieren?

Nun hat GNU-Emacs leider vor vielen Jahren seine eigene Idee statt Unicode entwickeln wollen, wie man die Zeichen der verschiedenen Sprachen codiert. Wer das noch weiß, wird vielleicht dem ganzen Werkzeug misstrauen, aber das ist nicht nötig.
Man kann eine einzelne Datei als Textdatei in utf-8 markieren, indem man in der ersten Zeilen einen Kommentar hat, der etwa so aussieht:

// -*- coding: utf-8-with-signature-unix -*-
// -*- coding: utf-8-with-signature -*-
// -*- coding: utf-8-unix -*-
// -*- coding: utf-8 -*-

Ab dann wird die Datei als Unicode gespeichert und auch beim nächsten öffnen interpretiert.
Das „-unix“ erzwingt einen Zeilenwechsel nur aus „ctrl-J“ (LF), statt „ctrl-M ctrl-J“ (CR LF), was in der Regel besser funktioniert, wenn man sowohl auf Win64/Win32 als auch auf Linux/Unix-Plattformen unterwegs ist.
Das -with-signature führt dazu, dass am Anfang der Datei eine „Signatur“ aus drei Bytes eingefügt wird, die ausdrückt, dass die Datei utf-8 ist. Leider wird diese Signatur nicht von allen Werkzeugen verstanden, aber wenn sie verstanden wird, ist das eigentlich der richtige Ansatz, weil man so wirklich utf-8 erkennen kann, ohne die ganze Datei vorher zu lesen.

Das läßt sich auch pro Endung festlegen, wenn man in .emacs so etwas schreibt:

(defvar utf8-suffix-regex nil "describes suffixes of files that are always utf8")

(setq utf8-suffix-regex "\\.\\(cs\\|scala\\|java\\|groovy\\)$")

(defun choose-encoding-by-suffix ()
  "Choose the encoding based on suffix"
  (interactive)
  (when (and (stringp buffer-file-name)
             (string-match utf8-suffix-regex buffer-file-name))
    (set-buffer-file-coding-system 'utf-8-unix nil t)))

(add-hook 'find-file-hook 'choose-encoding-by-suffix)

Wenn bei diesen impliziten Konvertierungen etwas schiefgeht, z.B. weil iso-8859-1 nach utf-8-Konvertierung zweimal statt einmal gelaufen ist, dann hilft recode. Man kann im Emacs die geöffnete Datei mit recode konvertieren:
Ctrl-X h ESC 1 ESC | recode utf8..latin1
Es empfiehlt sich, vorher eine Sicherungskopie anzulegen.

Dieselben Fragestellungen tauchen mit anderen Editoren und Entwicklungsumgebungen auch auf. In Eclipse und Netbeans kann man die Codierung von Dateien nach Endung, Projekt und auch für einzelne Dateien festlegen. Sicher wissen die vi-Spezialisten gut Bescheid, wie es mit vi geht.

Entscheidend ist, dass man sich bewusst für eine Codierung entscheidet. In Sprachen mit lateinischer Schrift dürfte das fast immer utf-8 sein, wenn man nicht „Altlasten“ hat. Das lässt sich mit geeigneten Editoren in den Griff bekommen.

Share Button

Einmal entwickeln – überall installieren

Das ist eine der großen Versprechungen von Java gewesen. Nun gab es drei oder bei genauerem Hinschauen etwas mehr als drei Varianten von Java, also „Micro-Edition“ (JavaME) für Toaster, Radios und Mobiltelefone, „Enterprise-Edition“ (JavaEE, JEE oder J2EE) für die ganz großen Server, die Sun auch gerne selber verkauft hat und „Standard-Edition“ (JavaSE) für „normale“ Aufgaben. Diese Aufteilung hat sich ein bisschen relativiert. Von JavaME hört man heute weniger, weil die Mobiltelefone heute leistungsfähig genug für JavaSE wären, wenn man es sich antun wollte, sogar für JavaEE. Die Programmierung von Toastern, Radios und Waschmaschinen ist ein Spezialgebiet, „embedded-Entwicklung“, mit dem man auch in der Informatik nur selten zu tun hat und diese Dinge nimmt man auch im Alltag kaum als Software wahr. JavaEE war in den ersten 10 Jahren einfach nur ein Rezept, um Programme langsamer, größer, komplizierter, störanfälliger und schlechter wartbar zu machen. Und man hatte plötzlich Abhängigkeiten vom Applikationsserver, die zwar idealerweise nicht den Programmtext selbst, wohl aber die Tonnen von XML-Konfiguration und die Deployment-Prozesse betraf. Da haben sich dann andere Frameworks entwickelt, zum Beispiel Spring, die etwas weniger schwerfällig daherkamen. Und für die Persistenz in JavaEE etablierte sich Hibernate und Eclipselink, was auch in JavaSE verwendbar ist. Nennt man nun eine Applikation, die mit Spring und Hibernate arbeitet, JavaEE oder JavaSE? Ich habe beides gehört. Jetzt sollte man bitte nicht versuchen, eine Applikation so zu entwickeln, dass sie wahlweise mit Spring und dem „Original-JavaEE“ läuft.

In den späten 90er-Jahren hatten Browser JavaSE eingebaut, damit man Applets schreiben konnte. So zeichnete es sich ab, dass jeder Rechner, den man kauft, eine gewisse Ausstattung bekommt, zum Beispiel einen Browser und eine Java-Installation. Dann hätten Java-Programme auf allen Desktop-Rechnern laufen können. Vielleicht müsste die Version hoch genug sein, weil Java sich natürlich weiterentwickelt hat und gelegentlich auch auf JavaVM-Ebene Erweiterungen nötig waren. Mit einem gute Software-Update-Mechanismus, wie wir ihn heute bei Linux-Distributionen oder bei den meisten Mobiltelefonen finden, hätte sich das lösen lassen. Dumm war nur, dass es eine Zeit gab, wo noch viele Anwender ein Modem mit 56k hatten und wo eine Java-Installation 80 MB groß war. Dann ist aber irgendwann, etwa nach dem Jahr 2000, die Java-Installation auf gängigen PCs selten geworden und man musste Endkunden bei der Installation von Java (wohlgemerkt 80 MB über das Modem) supporten, um ihre Java-Installation zum Laufen zu bringen. Das konnte sich nur in wenigen Fällen lohnen und man hat lieber auf Web-Applikationen gesetzt, was sich weitgehend durchgesetzt hat. Und die Browser können heute JavaScript, aber kein Java.

Bei den Mobiltelefonen gab es einige vielversprechende Anläufe, um Software-Entwicklung für eine Vielzahl von Geräten auf einmal zu ermöglichen. JavaME war eigentlich keine schlechte Idee, weil das auch auf recht kleinen Telefonen lief und auf den dicksten Smartphone könnte man es auch haben. Ein anderer Ansatz, der vielleicht ein bisschen weniger bekannt ist, ist die Verwendung des Qt-Frameworks für C++. Das ermöglicht auch auf einer Vielzahl von Geräten vom Arbeitsplatzrechner mit Linux oder MS-Windows bis zum Mobiltelefon „dieselbe“ Software laufen zu lassen, natürlich mit kleinen Anpassungen, um dem Benutzerinterface Rechnung zu tragen, und mit einer Neucompilierung für die Zielplattform. Diesen Weg hat Nokia verfolgt, als sie noch ernsthaft Mobiltelefone entwickelt haben und Qt-Applikationen liefen auf Symbian und Linux-Geräten (N900, N9), womit die Umstellung von Symbian auf Meego-Linux innerhalb kürzester Zeit eine reiche Auswahl an Apps für die neue Plattform ermöglicht hätte. Da Nokia sich aus der Mobiltelefonieentwicklung weitgehend und aus Meego und Symbian ganz zurückgezogen hat, bietet sich hier die Chance für neue Anbieter, wie zum Beispiel Jolla, mit dem Knowhow der ehemaligen Nokia-Mitarbeiter und deren vielversprechenden Ideen in den Markt einzusteigen und einen Teil der Rolle, die Nokia einmal hatte, zu übernehmen. Für native Apps auf dem Mobiltelefon ist aber aus heutiger Sicht die Dalvik-Engine von Android das mit Abstand zukunftsträchtigste Modell. Das ist letztlich eine weitere Java-„Edition“ und ermöglicht immerhin eine Lauffähigkeit auf dem überwiegenden Teil der neu verkauften Smartphones. Neue Linux-basierte Mobiltelefon-Betriebssysteme haben zum Teil die Möglichkeit integriert, für Dalvik geschriebene Android-Apps auszuführen. Aber einige Anbieter fahren bewusst die Schiene der Inkompatibilität ihrer Geräte und verlangen so eine Eigeneentwicklung nur für diese. Das schöne daran ist, dass dieser Ansatz scheitern wird, sobald die Marktanteile nicht mehr so groß sind. Hier werden sich wahrscheinlich Web-Applikationen durchsetzen.

Was Java betrifft, ist dessen Domäne heute neben Android-Telefonen im Serverbereich. Tatsächlich laufen Java-Applikationen ja mit hinreichend neuen JavaVM-Installationen oft auf allen Servern, wenn bei der Entwicklung gewisse Einschränkungen beachtet wurden. Ein Java-Programm, das seine Konfiguration in „C:/Programme/myCoolApp/config.properties“ sucht, wird auf Linux-Servern eher nicht das gewünschte finden, obwohl die „forward-slashes“ in Java-Programmen auch unter MS-Windows empfehlenswert sind und funktionieren. Aber es gibt immer noch genug potentielle Probleme, zum Beispiel mit Bibliotheken, die von der Java-Applikation benötigt werden, aber die inkomptabil zu der gewünschten Tomcat-Version sind, weil dort eine ältere Version derselben Bibliothek integriert und verwendet wird. Bis zu einem gewissen Grade lassen sich solche Probleme lösen und man findet immer wieder coole Ideen, die das ganz vollständig lösen wollen, wie z.B. OSGi. Mit entsprechendem Aufwand geht es auch tatsächlich in vielen Fällen, aber gewisse Bibliotheks-Inkomptabilitäten lassen sich nicht so leicht aus der Welt schaffen. so bleibt das „write once – run everywhere“ ein Versprechen, an dem viel Wahres dran ist, das aber auch seine Grenzen hat.

Nebenbei bemerkt ist das gar nicht so sehr die Eigenheit von Java. Qt mit C++ wurde schon erwähnt. Aber auch Perl, Ruby, Python, Oracle, C# (mit Mono), Lua, TeX und viele andere können ein ähnliches Versprechen wie Java abgeben und dieses ähnlich gut und mit ähnlichen Einschränkungen halten. Vielleicht ist es in Java schwieriger, nicht-triviale Dinge zu tun, die spezifisch für eine Plattform sind. Und vielleicht reicht Java in gewissen Bereichen mit diesem Versprechen etwas weiter als andere, aber für typische Server-Applikationen stimmt das „write-once-run-everywhere“ für Ruby-on-Rails mindestens genauso wie für Java-Web-Applikationen.

Share Button

Closures II (Java)

In Java gibt es schon seit recht frühen Versionen die sogenannten inneren Klassen.

Davon gibt es einige Ausprägungen:

  • statische innere Klassen
  • nicht-statische innere Klassen
  • anonyme innere Klassen

Beispiel 1: statische innere Klasse:

public class MyOuterClass {
  private static class MyInnerClass {
    ...
  }
}

Beispiel 2: nicht-statische innere Klasse:

public class MyOuterClass {
  private class MyInnerClass {
    ...
  }
}

Beispiel 3: anonyme innere Klasse:

public class MyOuterClass {
  public Runnable myMethod(..) {
    return new Runnable() {
      public void run() {
        ...
      }
   }
}

Sind die statischen inneren Klassen nichts anderes als weitere Klassen, die halt nur in ihrer Sichtbarkeit speziell für die umgebende Klasse zugänglich sind, so sind die anderen beiden Ausprägungen viel weitgehender in die umgebende Klasse integriert. Alle Attribute und Methoden der umgebenden Klasse können auch von der inneren Klasse aus angesprochen werden. Es fehlt leider ein zum „super“ analoges Schlüsselwort „outer“, so dass man sich mit Konstrukten wie „MyOuterClass.this.method()“ behelfen muss, wenn man etwas ansprechen will, was sowohl in der inneren als auch in der umgebenden Klasse vorkommt.

Genauer betrachtet gilt das für die statischen inneren Klassen und die statischen Attribute und Methoden der umgebenden Klasse natürlich auch, wobei man hier einfach „MyOuterClass.myStaticMethod()“ aufrufen kann.

Um in den nicht-statischen und anonymen inneren Klasse auf die umgebenden Attribute und Methoden zugreifen zu können, muss also implizit eine Instanz der äußeren Klasse referenziert werden. Das sollte man beachten, weil diese Referenz natürlich gehalten wird und so eine innere Klasse verhindert, dass die umgebende Instanz der Garbagecollection zugeführt wird. Beim Serialisieren kann ein scheinbar kleines Objekt riesig werden, wenn es einer solchen inneren Klasse entstammt.

Praktisch sind diese inneren Klassen, wenn man mit einem Objekt zwei verschiedene Interfaces befriedigen will. Es kann auch so etwas banales sein wie ein Collection, die mit getImmutable() eine Variante von sich selbst herausgibt, wo alle Methoden, die zu Änderungen führen würden, UnsupportedOperationException werfen.

Aber wie kommt man damit zu einem Ersatz für die Closures?

Vielleicht so:

public interface Function {
  public Y f(X x);
}

und dann kann man so etwas machen wie

public class C {
  public Function createAdder(final int x) {
    return new Function() {
      public Integer f(Integer xx) {
        return x+xx;
      }
    };
  }
  
  
  public static void main(String args[]) {
    C c = new C();
    Function ff = c.createAdder(3);
    for (int i = 0; i < 3; i++) {       System.out.println("i=" + i + " s=" + ff.f(i));     }     System.out.println();     ff = c.createAdder(90);     for (int i = 0; i < 3; i++) {       System.out.println("i=" + i + " s=" + ff.f(i));     }   } }

Das erzeugt etwa diese Ausgabe:

i=0 s=3
i=1 s=4
i=2 s=5

i=0 s=90
i=1 s=91
i=2 s=92

Diese Quelltexte kann man auf Github finden.

Es kommt also dem nahe, was man an dieser Stelle mit Perl machen kann.

Mit den Lambda-Funktionen oder Closures aus Java 8 sieht es dann etwa so aus:

  public Function createAdder(final int x) {
    return (xx) -> x+xx;
  }

Offensichtlich hat man dadurch also eine kürzere Schreibweise gewonnen, was Konstrukte dieser Art viel attraktiver macht.

Share Button

Closures I (Perl)

Alle „coolen“ Programmiersprachen haben sogenannte Closures. Java ist nicht cool, deshalb braucht man das dort nicht… 😉 Aber bitte bis zum Schluss weiterlesen…

Zunächst gibt es um diesen Begriff eine gewisse Verwirrung. Gemeint werden damit oft anonyme Funktionen, die man in einem Programm herumreichen kann, wie andere Werte und Objekte. Das ist so etwas, was man von funktionalen Programmiersprachen erwarten kann, auch wenn es noch nicht ausreicht, um eine Programmiersprache funktional zu machen. Die Funktionspointer in C sind etwas weniger als das, weil sie ja eine statisch definierte Funktion beschreiben, die erst im Kontext der Funktion, der sie als Parameter oder Variable sichtbar gemacht werden, anonym wirken. Andere Programmiersprachen wie Ruby, Perl, JavaScript, die meisten Lisp-Dialekte einschließlich Clojure, Scala, und viele andere mehr, in neueren Versionen wohl sogar C# und Java, haben solche anonymen Funktionen.

Zur Closure werden sie aber erst durch die Einbindung von Variablen aus dem Kontext, in dem sie definiert werden.
Hier wird etwas darüber geschrieben, wie Closures in Ruby funktionieren:
Closures in Ruby

Doch nun zu Perl. In diesem Zusammenhang muss man darauf achten, ob die lokalen Variablen mit „my“ oder mit „local“ deklariert.
In der Regel ist „my“ das, was man verwenden sollte und in der Regel macht es keinen Unterschied. Aber in diesem Fall sieht man es: Variablen, die mit my deklariert wurden, werden im definierenden Kontext der anonymen Funktion eingebunden, solche, die mit local deklariert wurden, im aufrufenden Kontext. Es gibt auch noch „our“, wobei der Unterschied zu „my“ in diesem Blogbeitrag erklärt ist. In diesem Beispiel verhält sich „our“ genauso wie „my“.

Hier ein Beispiel für die Verwendung von „my“:

#!/usr/bin/perl

# 2nd-order function, returns a reference to a function
sub createAdder($) {
    # local variable, will be accessed by anonymous function (early binding)
    my $x = $_[0];

    # define anonymous function, obtain reference to it and assign it to result
    my $result = ( sub {
                       # @_ refers to parameters of anonymous function
                       my $y = $_[0];
                       # $x comes from createAdder, $y comes from context of caller and $z is parameter of function
                       return ($x, $y, $x+$y);
                   });
    return $result;
}

# local variable, will not be accessed by called anonymous function
my $x = 100;

my $px = 30;

# obtain reference to function
my $ff = createAdder($px);

# call function
for (my $i = 0 ; $i < 3; $i++) {     my @arr = $ff->($i);
    my $arr_str = "( " . join(", ", @arr) . " )";
    print "x=$x i=$i px=30 (px, y, px+i)=", $arr_str , "\n";
}

print "\n";

$x = 1000;
$px = 60;

my $ff = createAdder(60);

# call function
for (my $i = 0 ; $i < 3; $i++) {     my @arr = $ff->($i);
    my $arr_str = "( " . join(", ", @arr) . " )";
    print "x=$x i=$i px=60 (px, i, px+i)=", $arr_str , "\n";
}

Man sieht also, daß das $x aus createAdder() in die anonyme Funktion eingebaut wird und es kommt diese Ausgabe:

x=100 i=0 px=30 (px, y, px+i)=( 30, 0, 30 )
x=100 i=1 px=30 (px, y, px+i)=( 30, 1, 31 )
x=100 i=2 px=30 (px, y, px+i)=( 30, 2, 32 )

x=1000 i=0 px=60 (px, i, px+i)=( 60, 0, 60 )
x=1000 i=1 px=60 (px, i, px+i)=( 60, 1, 61 )
x=1000 i=2 px=60 (px, i, px+i)=( 60, 2, 62 )

Mit local sieht es so aus:

#!/usr/bin/perl

# 2nd-order function, returns a reference to a function
sub createAdder($) {
    # local variable, will be ignored by anonymous function (late binding)
    local $x = $_[1];

    # define anonymous function, obtain reference to it and assign it to result
    my $result = ( sub {
                       # @_ refers to parameters of anonymous function
                       my $y = $_[0];
                       # $x comes from createAdder, $y comes from context of caller and $z is parameter of function
                       return ($x, $y, $x+$y);
                   });
    return $result;
}

# local variable, will be accessed as $y by anonymous function
local $x = 100;

local $px = 30;

# obtain reference to function
my $ff = createAdder($px);

# call function
for (my $i = 0 ; $i < 3; $i++) {     my @arr = $ff->($i);
    my $arr_str = "( " . join(", ", @arr) . " )";
    print "x=$x i=$i px=30 (px, y, px+i)=", $arr_str , "\n";
}

print "\n";

$x = 1000;
$px = 60;

$ff = createAdder(60);

# call function
for (my $i = 0 ; $i < 3; $i++) {     my @arr = $ff->($i);
    my $arr_str = "( " . join(", ", @arr) . " )";
    print "x=$x i=$i px=60 (px, i, px+i)=", $arr_str , "\n";
}

Man sieht also, daß diesmal das $x aus dem aufrufenden Kontext in der anonyme Funktion verwendet wird und es kommt diese Ausgabe:

x=100 i=0 px=30 (px, y, px+i)=( 100, 0, 100 )
x=100 i=1 px=30 (px, y, px+i)=( 100, 1, 101 )
x=100 i=2 px=30 (px, y, px+i)=( 100, 2, 102 )

x=1000 i=0 px=60 (px, i, px+i)=( 1000, 0, 1000 )
x=1000 i=1 px=60 (px, i, px+i)=( 1000, 1, 1001 )
x=1000 i=2 px=60 (px, i, px+i)=( 1000, 2, 1002 )

Diese Programme haben jeweils eine Funktion 2. Ordnung, createAdder(), die selbst eine Funktion zurückgibt.

Nun stellt sich aber heraus, dass Java ein sehr ähnliches, natürlich einmal wieder etwas umständlicheres Konstrukt hat. Das sind anonyme innere Klassen, die ein Interface mit genau einer Methode implementieren. Und Java 8 hat die Closures jetzt auch ohne diesen Umweg. Aber das ist sicher einmal ein anderer Blog-Beitrag.

Share Button