Pages

Saturday, November 19, 2011

Ceylon - Eine neue Sprache für die JVM

Ceylon befindet sich aktuell in der Entwicklung aber ein Blick auf den Sprachumfang gewährt einem jetzt schon tiefe Einblicke in Konzepte und Funktionsweisen. Außerdem sind Diskussionen und Entscheidungen zu konkreten Elementen der Sprache transparent. Die Ziele der Entwickler von Ceylon sind hoch gesteckt. Nach eigenen Angaben liegt der Fokus von Ceylon auf:

  1. Eleganz
  2. Lesbarkeit
  3. Typsicherheit
  4. Meta-Programmierung

Die Programmierung soll wesentlich einfacher sein als mit Java. Besonders in Bereichen von generischer Typsicherheit räumt Ceylon auf. Die Sprache ist mit funktionalen Bestandteilen angereichert, verliert sich aber nicht in verketteten Operationen ohne Seiteneffekte sondern versucht das Beste aus den Welten der Objektorientierung und der funktionalen Programmierung zu vereinen.


Entwicklung

Aktuell existiert ein Plug-In für Eclipse, sodass diese IDE jetzt schon für Ceylon zur Verfügung steht:

Obwohl man bereits einfache Applikationen schreiben kann, stolpert man früh auf kleinere Bugs in der Sprache und Ungereimtheiten in der IDE. Beispielsweise funktioniert das Autoboxing und -unboxing derzeit nicht konsistent.
Derzeit wird kein offizieller Release-Termin für Ceylon veröffentlicht. Konkrete zeitliche Angaben über die Erreichung von Meilensteinen fehlen ebenso. Inhaltlich ist jedoch ausreichend Transparenz vorhanden um sich ausmalen zu können wie zukünftig Ceylon aussehen wird:

Wörterbuch

Java               Ceylon
implements         satisfies
public             shared (kontextabhängig)
protected          - 

private            -
abstract           formal (Attribute/Methoden)


Merkmale (Ceylon vs Java)

Im Folgenden habe ich die aus meiner Sicht interessantesten Merkmale von Ceylon zusammengefasst.

Keine NullPointerException:
  • Typsicherheit bei null
  • Nullwerte sind Inkarnationen der Klasse Nothing
  • Mögliche null-Werte müssen den Datentyp bei der Deklaration erweitern:  
    • String? vorname  = "Frank"
    • String|Nothing vorname = "Frank";
  • Korrespondierendes exists-Konstrukt
    • if (exists vorname) {...}

Nur ein Zugriffsmodifikator: shared
  • Gültigkeitsbereich von shared ist dynamisch
  • Kontextabhängig Funktionsweise
  • Klassen sind standardmäßig Paketlokal, shared muss explizit deklariert werden
  • Eine shared Klasse kann keine Paketlokalen Attribute besitzen
  • Protected kann nicht abgebildet werden

Enumerierte Ableitungen:
  • Es kann über enumerierte Subtypen eine direkte Verknüpfung der Basisklasse zu der Anzahl der entsprechenden Subtypen hergestellt werden
  • Dadurch wird zur Compile-Zeit sichergestellt, dass alle Ableitungen in polymorphen Aufrufen explizit im Code behandelt werden 
Beispiel:
abstract class Tier() of Hund | Katze | Maus { ... }
...
void sagHallo(Tier tier) {
    switch (tier)
    case (is Hund) { //wuff... }
    case (is Maus) { //pief... }
    // compile Error: Katze muss ebenfalls behandelt werden
}
 

Attribute:
  • Können einfache Zustände halten
  • Können getter/setter implizieren ohne sie explizit zu deklarieren 
  • Sind polymorph und überschreibbar
  • Können in Verbindung mit Closures verwendet werden

Initialisierung:

  • Fokus liegt auf einem einheitlichen Initialisierungs Look-And-Feel von Klassenattributen und lokalen Variablen. (anders als in Java)
  • Keine Konstruktoren, Initialisierung wird aufgeteilt in:
    • Initialisierungsparameter (werden hinter den Klassennamen geschrieben)
    • Initialisierungscode wird direkt in die Klasse geschrieben

Frage: Wie führe ich unterschiedliche Initialisierungen durch z.B. für Testklassen die in Java über den Good Citizen Konstruktor abgebildet werden?
"Am Easy to test- all dependent object I use can be passed to me, often in my constructor (typically as Mock Objects)."

Funktionale Ausrichtung:
  • Klassendeklarationen ähneln eher Methodendeklarationen
  • Eine Klasse kann auch als eine Funktion verstanden werden welche ein Closure mit den eigenen lokalen Variablen liefert.

Type Aliase und Type Inferenzen
  • Für lokale Variablen kann der Typ geschlussfolgert werden
    • value tiere = LinkedList { "Hund", "Katze", "Maus" }; 
  • Typedefs sind möglich, sollten aber sparsam verwendet werden
    • interface tiere = List<Tier>;

High-Order functions
  • Funktionen können andere Funktionen als Parameter entgegen nehmen
    • void fuettern(Essen essen, void fressen())

Vereinfachte Generics
  • Keine Java Wildcards
  • Keine Raw-Types
  • Vergegenständlichte Deklaration

Operanden Polymorphismus
  • Es existiert eine feste Zuordnung von Operanden zu Interfaces
    • == zu Equality
    • < zu Comparable
    • * zu Numeric
  • Alle Klassen, welche Equality implementieren können mit == verglichen werden


Fazit

Die Typsicherheit bei Nullwerten und die Wertsicherheit bei Subklassen sind auf den ersten Blick sehr interessant. Ob die Generics wesentlich einfacher geworden sind, ist meiner Ansicht nach fraglich, da die "declaration-site variance" von Ceylon - Generics um eine neues Konzept erweitert.
Den Ansatz shared als einzigen Zugriffsmodifikator zu verwenden, betrachte ich ebenfalls skeptisch. In Java ermöglichen public, protected, default und private das Setzen von feineren Nuancen und damit Designabsichten ausdrucksstark zu untermalen. Deshalb geht die gewonnene Dynamik von shared auf kosten der ausdrucksstarken Lesbarkeit. Der Reflection API von Java stellt Ceylon ein typsicheres Meta-Programmiermodell entgegen, welches ich als weiteres Highlight sehe und für mich die größte Stärke der Sprache bedeutet. So ist es beispielsweise möglich Objektinstanziierungen oder Methodenaufrufe zu "intercepten" ohne Code zu implementieren welcher nach entsprechenden Interecption Annotationen sucht. Das Metamodell von Ceylon erledigt das "out of the box" zur Class-Loading-Time.

    1 comment:

    1. Interessant finde ich auch die Eigenschaft, das Getter/Setter nicht mehr explizit codiert werden müssen.

      ReplyDelete