Saturday, September 17, 2011

Project Lombok

da der Scala-Weg derzeit immer noch zu kontrovers diskutiert wird, habe ich mir Projekt Lombok angesehen. Projekt Lombok pimpt die Sprache Scala um Bytecode-Generatoren und ein neues Sprachfeature. Es arbeitet mit Annotations und einen Java-Agent, der mit javac, eclipsec und gwtc funktioniert.

Projekt Lombok stellt vor allem drei wichtige Features für die Java-Entwicklung bereit:

@Data-Annotation
Äquivalent zu einer Scala-Caseclass. Erzeugt automatisch Konstruktor für finale Felder, getter,setter, equals/hashcode und toString.

class Person{
    final String name;
    final int age;
}


Vorteile:
- Weniger Code -> Bessere Übersicht, und keine Bugs (falsche Felder im Konstruktor gesetzt, equals/hashcode nicht auf neue Felder abgestimmt etc)
- Da reine Datenstrukturen leichter erstellbar sind, werden sie auch häufiger genutzt werden. Pusht funktionale Programmierung in Java, da Daten und Funktion leichter trennbar sind
- Final-Felder ohne Konstruktor-Overhead
- Besondere Getter und Setter die mehr als nur ihren Job machen, werden die leicht erkennbar
- Alle Methoden können immer noch "manuell" definiert werden!

val-Keyword
Godlike: Bei finalen lokale Variablen kann statt des Typs einfach val geschrieben werden, wie in Scala:

val s = new ArrayList<String>();
final ArrayList<String> s = new ArrayList<String>();

Technisch gesehen ist val ein syntethischer typ aus org.lombok, den der java-agent bei der Compilation gegen den richtigen Typ austauscht.

Vorteile:
- Bevorzugt massiv die Verwendung finaler Variablen, was gut ist
- Pusht Generics, die ebenfalls gut sind
- Variablen, die wirklich veränderlich sind jetzt leichter erkennbar und werden mit Typangaben "bestraft".
Das ganze funktioniert nur bei lokalen Variablen, nicht bei Parametern und Feldern. Ist in Scala aber oft nicht anders.

Drittens gibt es noch die Delegate-Annotation, die dann hilft wenn eine Klasse zwei Interfaces "mixen" soll. Ermöglicht teilweise ähnliche Funktionen wie Mixins in Scala. Das Feature ist sehr gut für spätere Refactoring-Jobs geeignet:

public class StringList extends AbstractList<Character> implements CharSequence{
  @Delegate
  final String data;
  public StringList(String s) { data = s; }
  public Character get(int i){ return data.charAt(i); }
  pulblic int size() { return data.length(); }
}

Obiges Konstrukt erzeugt einen Mixin aus List<Character> und CharSequence. Die Schnittstellen dafür bietet ja bereits durch seine Interface-Vererbung an. Die Delegates ermöglichen es nun aber auch, die Implementierungen zu vererben - besser: weiterzuleiten:
StringList enthält jetzt alle von AbstractList geerbten Methoden UND alle Methoden von String.
Je nach Wunsch hätte man auch die Delegation einschränken können, indem man nur die Methoden des Interfaces CharSequence delegiert.


Local Typeinference, Case-Classes, "Mixins": Gute Zeiten für anspruchsvolle Java-Entwicklung. :-)


Stabilität von Lombok
Eine offene Frage mag die nach der Stabilität bzw. Portierbarkeit sein. Die Project-Page gibt dazu schon detaillierte Infos, ich kann aber noch meine Erfahrungen beisteuern (eclipse-only):
data: extrem stabil
val: stabil, man sollte es aber nicht an den falschen Stellen (Methoden-Parameter, Klassenfelder) einbauen. Dann gibts schon mal eclipse-Fehlermeldungen (AST-Exceptions). Diese haben aber keine weiteren Auswirkungen.
delegate: nicht stabil. Das Compilieren klappt ohne Probleme, der eclipse-Editor kommt damit aber noch nicht so gut zurecht. Auch hier kommen öfters AST-Exception Fehlermeldungen ohne weitere Auswirkungen. Da es eh das am selten genutzteste Feature ist, ist das aber nicht so schlimm, da es nur für im Editor offene Klassen gilt.

Exit-Strategie?
Was wenn man es nicht mehr einsetzen will? Was wenn man ein besonderes Environment hat in dem keine Java-Agents greifen? In dem Fall hat Lombok gegenüber der Wahl einer Alternativsprache einen Riesenvorteil: Das Projekteigene Tool kann aus mit Lombok-Annotationen versehenen Klassen direkt die entsprechenden Gegenstücke in "reinem" Javasourcecode erzeugen.
Wenn sich dass Tool also mitten im Projekt als fehlerhaft oder falsche Wahl erweist dann ist der Wechsel trivial: Delombok auf die Quellen aufrufen und man kann so mit dem Erzeugen Quellcode direkt weiterarbeiten.

Saturday, September 10, 2011

Java => Scala: Scalava

So many are lamenting about how crappy Java is and that we need a replacement (like me). But do we? Shouldn't we just start by using the tools which java gives in the most awesome way possible?

Ingredients needed:
Java 6
guava.jar (google-collections)
lombok.jar (add to classpath and IDE)

Features:

Packages (included in java)
Originally, they seem to be designed to contain "modules", while public was only intended for those (perhaps few) ankle points where they were communicating with the "outer" world. That idea has a lot (and i mean a lot) of problems (like interfaces and their demand for public methods), so it does not make sense to really track that route consistently. But it still does make sense to use packages to ease your development:
Advantages:
- No visibility modifier needed (good for methods)
- multiple toplevel-classes in a single file (good for implementing many small classes)
- Allows you to make certain "dangerous" stuff available in a broader sense than private but still restricted to a fixed amount of classes you can control - unlike protected and public.

Unnamed Fields, classes and Methods
Scala uses conventions to make methods invisible and provide singletons.
By using a trick, you an do this in java as well: Just create elements which consist of nothing more than an underscore. Sounds strange? It is, in fact just a convention with nice effects:

package functions: class _ {}
Create a PACKAGE-PRIVATE _ in each of your packages to store static functions which do not belong to objects. That way, you have a place where all your fresh functions can end up, until they provide usefull enough to get their own "utilsXYZ".
In the end, no instantiable class should have static methods beside factories, and this is the way to enforce this and help to re-use code!
Same goes for constants
Note that the class must be package-private: otherwise, you end up with WAY to many _ in your code-completion!

Singletons:
Singletons can be stored in a public _ field, which reduces their code bloat and makes the name nicely readable: Singleton._ instead of Singleton.get() or Singleton.INSTANCE crap.

Singleton-Factories
Useful trick for interfaces:
public interface List{
   ListFactory _ = new ListFactory();
   class ListFactory{
      LIst of(){ ... }
   }}

Usage:
List._.of()

Tuesday, December 21, 2010

Meine wenigen Kritikpunkte an Scala

Liste ich mal kurz hier auf. Auch wenn ich ein großer Fan der Sprache bin - an einigen Stellen ist Scala meiner Meinung nach einfach unnötig komplex wenn es einfachere Wege gäbe.

  • OperatorPrecedence und Auswertungsrichtung: Beides sind meiner Meinung nach syntaktische Konstrukte die in Ausnahmefällen Vorteile bringen (mathematische Berechnungen) aber keinen inhärenten Mehrwert haben. Worin liegt der Vorteil, 3 + (4 * 2) in der Mathematischen Reihenfolge zu berechnen, wenn eine stupide Auswertung von links nach rechts á la Smalltalk viel einheitlicher gewesen wäre? Klammern als Gruppierungsmerkmal reichen doch aus.. Zur Richtung: Ich mag :: wirklich, aber ist es wirklich so wichtig?
  • MethodOverloading: Das hier ist ganz klar ein Erbe von Java, und wahrscheinlich unvermeidlich zu integrieren gewesen. In Scala ist es aber viel weniger nützlich als in Java, da strukturelle Typen und implizite Konvertierungen viele der Usecases abfangen. Dafür wird man dann mit den komplizierten Regeln und der Uneinheitlichkeit noch viel stärker abgestraft. Hätte man vllt. nur auf der Call-Side in einem Legacy-Mode, aber nicht in eigenen Klassen unterstützen sollen.
  • Unterschiedliche Notationsarten: Das ist wohl eines der schwierigsten Themen. Scala kennt die .-Notation, die Operatornotation und die Prefíxnotation zum Aufruf von Funktionen. Eine hätte gereicht - Meiner Ansicht nach sollte man nur die Operatornotation unterstützen, da diese die größte Flexibilität bietet und in dem Moment leicht lesbar wird, in dem das Method-Overloading und die Precedence ebenfalls weggefallen sind.
  • Einheitlichere Behandlung von Tupeln und Parameterlisten: Baut wiederum auf den vorherigen Punkten auf. Sind diese alle erfüllt, gäbe es weniger Corner-Cases bei denen der Compiler einen Syntaxfehler anmarkern würde, wenn Tupel und Parameterlisten nicht austauschbar verwendet werden können.
  • Wegfall der abgekürzten Schreibweise für Funktionen mit _ : Closures sind in kaum einer Sprache so leicht und Flexibel deklarierbar wie in scala: { () => println ("hello") }; {x => x + x}. Warum also hat man die noch stärker reduzierte Schreibweise mit dem Underscore eingeführt? Neben der meist abnehmenden Lesbarkeit ("ist das jetzt eine Funktion..?") gibt es auch unzählige Fallstricke bei der Deklaration. Wenn man Glück hat dann führen diese dazu dass einem der Compiler den Code vor die Füße wirft. Wenn man Glück hat...
  • Kein Inline-XML mehr: Das wäre wohl der erste Punkt auf der Liste der Schöpfer von Scala. Dieses Feature macht die Sprache nur komplizierter, ist an ein konkretes mittelmäßiges Dateiformat gebunden und macht Compilern und der IDE das Leben zur Hölle.
Wie man sieht kritisiere ich eigentlich nur die oft zu komplizierten Syntaxmöglichkeiten. Klar ist dass die Änderungen an der Sprache nicht vorgenommen werden, aber schade ist es trotzdem. Eine deutliche Vereinfachung der Syntax wäre zur Komplexitätsreduktion sicher viel hilfreicher als eine Reduktion von Features (implizite Konvertierungen...).
Ein weiterer Vorteil wäre dass vllt. eine Konzentration auf das wesentliche, d.h. Operationen zugunsten von weniger syntaktischen Quengeleien erreicht werden könnte. Zu oft scheint mancher Scala-Code nur vom Gedanken getrieben zu sein, eine möglichst mächtige DSL zu entwickeln.

Ausserdem habe ich auch noch einen Punkt den ich zusätzlich noch gerne "drin" sehen würde:
  • Zwanghafte Behandlung von Rückgabewerten außer (): Wenn Scala ähnlich wie Pascal das Entgegennehmen der Rückgabewerte zwanghaft machen würde, dann würde das Wegfallen der Checked-Exceptions weniger wehtun. In diesem Fall können Unit-Operationen mit Side-Effects dann als Rückgabewert Option[Exception] verwenden, und der Programmierer würde vom Compiler dazu gezwungen, diesen Wert zumindest entgegenzunehmen - und so nicht zu vergessen!

Monday, December 20, 2010

NeoLinq: Typdeklarationen

Die ersten Ideen zur Umsetzung des Typkonzepts...

Array = [ A ->
  length: Int
  isEmpty = length > 0
  subarray = {(pos: Int, count = length - pos) ->
    // native stuff
  }
]

String = [
  chars: Array[Char]
  isEmpty = chars.length > 0
  substring = { (pos: Int, count = chars.length - pos) ->   
    String new (
      chars = chars subarray (pos, count)
    )
  }
]

Diese Methoden stellen natürlich nur - nicht korrekte Beispielimplementierungen dar, bei denen noch viele interessante Fragen bzgl. des Designs zu klären sind:
1: Es wird später einen Weg geben müssen Methoden "nativ", d.h. in C zu implementieren. Dies ist der Weg, viele der abstrakten Konzepte und den Verzicht von Keywords sogar bei if und else dennoch performant zu gestalten.
2: Wird es default-parameter geben, d.h. defaults bei Tupeln? Der Code oben zeigt das alte Problem, dass diese auch direkt durchgereicht werden müssen. Vllt erübrigt sich dies aber, wenn ich wirklich das Tupel als ganzes durchreiche. Mal sehen.
3. Wie werden Konstruktoren funktionieren? Der Code oben zeigt den Weg, die "new" Funktion als generische Funktion des "Typ" Interfaces anzubieten, aber der weitere Code macht dann keinen Sinn.

Das oben ist noch kein ausformulierter Code, dient aber wie man sieht zur Definition von Klassen, d.h. implementierten Typen. Da das Typsystem vorsieht, auf oberster Ebene strukturell zu sein, und erst bei der Implementationsebene nominell, d.h. auf Klassenhierarchien, zu arbeiten, ergibt sich noch die Nötigkeit, Strukturelle Typen zu definieren.

Die Regel dazu lautet: Das ":"-Zeichen ist dem Typennamensraum vorbehalten. Dadurch ergibt sich für strukturelle Typen:

:Comparable = [
  compareTo(t :Any)
]
Dies ist ein struktureller Typ, auf den jeder Typ passt der eine CompareTo Methode hat.

Saturday, December 18, 2010

NeoLinq: Tupeldeklarationen

Eine der großen offenen Fragen ist, ob Tupelschnittstellen () zur Deklaration verwenden. Ich experimentiere damit, Typen stehts in [ ] einzuschließen, so möglich. Dann hätte jedes Konzept eine eigene Klammersprache:

(): Auswertungsreihenfolge
{}: Blockdefinition. Codeblöcke für spätere Ausführung.
[]: Typendefinition. Codeblöcke für spätere Ausführung UND public interfaces.


NUR durch Typen ist es möglich, zwischen Scopes zu wechseln! Das normale Scoping sieht vor, dass Namen nach unten durchlässig sind, d.h. in jedem Block sind die Werte der darüberliegenden Scopes bekannt, so sie nicht durch Name Shadowing, d.h. überlagen mit gleichen Namen, unsichtbar sind. Typen hingegen haben die Aufgabe, Schnittstellen nach oben sichtbar zu machen.

Zurück zu den Tupeln:
Es ist immer noch nicht definiert, was Tupel eigentlich sind. Was für eine Art von Typ stellen sie dar? Wenn man Tupel mit der []-Syntax für Typen deklariert, dann müssen Tupel vollwertig ins Typsystem passen bzw. DAS TYPSYSTEM ALS SOLCHES ERWEITERN. Verwendet man die ()-Syntax bei der Deklaration, dann werden Tupel nur als ein Typ unter vielen gesehen.
Anders formuliert:

[a: String, b: Int] oder (a: String, b: String)?


Wenn man das ganze so sieht scheint die zweite Variante besser zu sein. Tupel sollen Werte gruppieren und stellen eine Klasse von Typen dar. Aber die Mengen-Eigenschaft eines Tupels generisch auf einen Typen zu übertragen fühlt sich einfach falsch an.


Aber es gibt ja noch ein Konstrukt:
Ebenso wie das Typsystem ist auch das PatternMatching noch nicht ausformuliert, auch wenn es meiner Meinung nach ein ebenso fester Bestandteil sein muss.
PatternMatching kann große Auswirkungen auf Tupel haben: Genaugenommen ist (a: String, b:String) zum einen eine Deklaration, zum anderen aber ein Muster zum Zerlegen eines Typs in Variablen, inkl. (optionaler) Typannotationen.


Wenn aber Tupel-Deklarationen ein Sonderfall des Matching sind, dann sollte man versuchen, die Deklarationsyntax  daran zu orientieren. Vllt. bietet das Int* Konstrukt neue Ideen für das Matching von Mengen! Für Tupel wäre es auf jeden Fall erforderlich..

Friday, December 17, 2010

NeoLinq: Basics

1. Definitionen:


// kommentar => kommentar.
name = Wert     // definiert einen unveränderlichen wert. Der Bezeichner steht also ab jetzt für Wert.
name := Wert     // definiert auch einen wert, aber einen veränderlichen. Es handelt sich also nicht um eine Gleichsetzung,  sondern eine Zuweisung.
fun = {}     // definiert eine parameterlose funktion mit dem rückgabewert ()
argfun = { i :Int -> i + 2 }     // definiert eine funktion mit dem parameter i vom Typ Int und dem Rückgabewert i + 2 vom Ty
val = argfun 3 // val erhält den rückgabe wert von argfun, aufgerufen mit dem parameter 3. das ergebnis: 5.


Beispiele:


wert = 2
inkrement = { i: Int -> i + 1 }
ergebnis := inkrement wert    // 3


Und jetzt mal was krankes mit nem Haufen neuer Syntax:
doppelEffektPlusEins = { (wert: Int, f: {Int -> Int}) ->
   f (f wert) + 1
}


// ergebnis (variable) überschreiben
ergebnis := doppelEffektPlusEins (ergebnis, inkrement)




Das Ganze hat viel mit Smalltalk zu tun:
1. Auswertungsreihenfolge: Starr von links nach rechts. Durch Klammern beeinflussbar
2. Reine Infix Notation. Keine Punkte als Trennzeichen, stattdessen whitespace
3. Operatoren sind einfach Mehodennamen
4. Blöcke, d.h. Funktionen werden als Werte definiert und sind ausführbar. Sie können als echte Closures auf alle Werte der äußeren Scopes zugreifen und diese beeinflusse.
5. Keine Schlüsselwörter. Diese können über Funktionen abgebildet werden.

Funktionale Gedanken:
1. Daten sind immutable. ABER: Imperative Zuweisungen und Definition sind mit der := Syntax, die explizit Varablen deklariert, möglich.
2. Typsystem: Tupel sind echte Datentypen. Typen werde a la Scala annotiert. Typen stehen im Vordergrund, keine Klassen. Vllt sind diese aber nachher dasselbe...

"Mein Zeuch"
1. Es gibt Blöcke mit einem Parameter und ohne. Mehrere Parameter sind nur ein Sonderfall: Hier wird ein Tupel mit festen Typen definiert. Varargs werden ähnlich behandelt werden.
2. Durch Klammern lässt sich die Verarbeitungsreihenfolge beeinflussen. Die Aufgabe ist hier und bei Tupeln aber dieselbe: Klammern vereinigen Werte. Die Kommas bei Tupeln stellen davon nur lediglich einenSsonderfall dar, da hier nicht mehrere Dinge in einen Wert verwandelt werden, sondern mehrere Werte parallel existieren.


Ich weiß noch nicht genau wo meine Ideen hingehen. Es sieht aber doch sehr stark nach OO aus, aber auch mit einer strukturellen Komponente. Wahrscheinlich Scala sehr ähnlich: Strukturelles Typsystem, dass Nominelle Typen (Klassen) ermöglicht und OO-Subtypisierung erlaubt. Und am Ende steht dann irgendwann ein "vereinfachtes" Scala (weil nicht JVM-Kompatibel und es nicht implementiert wird). Wenns gut läuft..

Eine neue Sprache: NeoLinq (Vorläufiger Titel)

Das Ziel: Eine Lernsprache, die neue Möglichkeiten aufzeigen soll, zu Abstrahieren und einfachen Code zu entwickeln, aber dennoch performant ist - einen guten Compiler vorrausgesetzt. Es soll kein neuer Stern am Firmament der Programmierung werden.

Inspirationen
  • Teile von Haskells Typsystem
  • Funktionale Programmierung
  • Lisps Syntax und Grundgedanke, sich auf das Wesentliche zu beschränken: Funktionen.
  • Scalas Konzept, ein reichhaltiges Set an Features bereitzustellen und dem Programmierer die Wahl zu lassen.
Idee:
Der Sprachkern soll NUR die elementaren Bestandteile enthalten und so wenig Konstrukte wie Menschenmöglich beiinhalten OHNE die Ausdruckskraft zu behindern.

Features:
  • Operatoren
    • Unbekannt. Bis auf wenige Ausnahme können allerdings sämtliche US-ASCII-Zeichen als Bezeichner verwendet werden. + ist z.B. ein gültiger Name für eine Methode von Int. Kein Operator! Auch boolschen Operatoren wird es nur als normale Methoden geben. (Teilweise Scala)
    • Keine Operator-Precedence: Folgt daraus. Aller Code wird von links nach rechts ausgewertet. Operatorenreihenfolgen bieten keinen Mehrwert bei der Programmierung. (LISP)
  • Namespaces und Scopes
    • Maximal gibt es zwei Namespaces: Wert und Typ.  Ob es wirklich einen separaten Namespace für Typen gibt, ist noch ungeklärt.
    • Uniform Access Principle:  Methoden und Felder teilen sich den selben Namespace. Es gibt keinen syntaktischen Unterschied zwischen den beiden. (Scala, Eiffel)
    • Method-Overloading / Multimethods: In einem Scope kann es immer nur eine Methode mit einem Namen geben, die gegen genau einen Typen dispatcht wird. Overloading ist nicht erlaubt  (Eiffel)
    • Case-Sensitiv: Horst und horst sind unterschiedliche Bezeichner. In Sprachen mit wenigen Namespaces zwingende Vorraussetzung.
  • Nicht technik-getrieben
    • Es soll kein neues C entwickelt werden - keine Zugeständnisse an die Technik. Dennoch sollte die Sprache nur Konzepte erhalten, für die zumindest eine einfache und performante Umsetzung denkbar ist - wie sie erst mal dahingestellt. Gegenbeispiel: Go.
    • Keine Kompatibilität zu JVM o. CLI: Folgt aus dem ersteren. Falls die Sprache jemals fertig würde, könnte man sich immer noch Gedanken darüber machen, mit welchen Modifikationen sie zu vorhandenen RTEs kompatibel werden kann. Gegenbeispiel: Scala.
  • Keine Schlüsselwörter
    • Die Sprache ist so minimalistisch und generisch wie möglich. Keine Schlüsselwörter, da diese den Compilern die Arbeit erschweren und den Sprachkern weniger generisch machen.
    • Sprachkonstrukte durch Methoden: Soweit möglich sollen alle Sprachkonstrukte durch Methoden abgebildet werden (Lisp).
  • Ideen für Datentypen
    • Technische Basistypen: Int, Float, und wahrscheinlich auch Char und String sowie weitere numerische. To be discussed.
    • Parameterlose Funktion (als kompatibles Gegenstück zum Wert. Nicht als Closure verwendbar).
    • Funktion mit einem Parameter (echte Closure)
    • Gruppierung von Werten durch Tupel und Sequenzen (array-ersatz). Ersetzen mehrere Parameter UND varargs vollständig.
    • Typ. Typen sind selbst Datentypen, die Funktionen wie z.B. Subtypisierung u.ä. anbieten.
    • Typ mit Parameter, a.k.a. Generics. Der Typ kann durch den "Typkonstruktor" in verschiedenen Ausprägungen manifestiert werden. Realisierung offen, könnte sich an Scala orientieren.
    • Unit (=> void). Konstante für das leere Tupel. 
    • Kein null!
Weitere Erklärungen und erste Syntaxideen to come...
Wenn ich mehr gesammelt habe werde ich das Ganze mal zur Diskussion stellen. Vllt entwicklen sich ja in größerer Gemeinschaft ein paar Tolle Ideen.