© DrHitch/Shutterstock.com
Clojure

1 Clojure - Status quo und Ausblick


Spätestens nach der Einführung des x-ten Sprachfeatures der Haus-und-Hof-Programmiersprache stellt sich die Programmiererin folgende Frage: Gibt es denn keine Sprache, die mit der mindestmöglichen Anzahl von Sprachmerkmalen ihr Auskommen findet, auf diesen aufbaut, anstatt nach und nach Konzepte in die Sprache einzuführen und in vorhandene Schnittstellen zu integrieren? Eine der möglichen Antworten wurde bereits vor etwa fünfzig Jahren am Massachusetts Institute of Technology (MIT) gefunden. John McCarthy ist der gedankliche Vater von Lisp. Mit seinen Ausführungen über das so genannte „Lambda-Kalkül“ legte er die mathematische Basis für eine der beiden bis dato am längsten in der Praxis existierenden Programmiersprachen. Es gibt kein „reines“ Lisp. Vielmehr gibt es unterschiedliche Varianten, die Lisp-Dialekte. Die bekanntesten sind Common Lisp und Scheme. Im Gegensatz zum Aufbau europäischer Informatikstudien hat Lisp im amerikanischen Universitätsumfeld einen fixen Platz in der Ausbildung neben Java, C, C++ und den anderen üblichen Verdächtigen.

Clojure ist ein seit 2007 bestehender Lisp-Dialekt. Der Begriff „Clojure“ soll auf Closures, Java und Lisp anspielen. „Clojure“ dient nicht nur als reiner Bezeichner für die Programmiersprache, sondern umfasst auch die aus mehreren Komponenten bestehende Laufzeitumgebung. Wichtiger Bestandteil ist dabei die REPL (engl. Read-Eval-Print Loop), ein einfaches konsolenähnliches Interface, das die schnelle Ausführung von Clojure-Code erlaubt. Die Laufzeitumgebung setzt dabei auf der JVM auf, generiert also an bestimmten Stellen JVM-kompatiblen Bytecode und kann somit in bestehenden Java-Umgebungen ausgeführt werden.

Clojure 1.6.0 setzt auf JDK 6 auf (1.5.x war mit JDK 5 kompatibel). Clojure-Programme müssen daher mindestens mit dem JRE 6 ausgeführt werden.

LIStProcessing

Lisp steht für LIStProcessing, also Listenverarbeitung. Die Liste hat dabei neben der Verwendung als Datenstruktur in herkömmlichen Java-Programmen (man denke an die java.util.ArrayList) eine besondere Stellung. Listen werden verwendet, um den eigentlichen Code zu formulieren. Wenn man so will, besteht jedes Programm aus einer Menge von Codelisten. Eine Liste repräsentiert zur Laufzeit einen Funktionsaufruf. Damit sind nicht nur Aufrufe im herkömmlichen Sinne gemeint. Clojure ist komplett funktional und verwendet Funktionen beispielsweise auch, um Variablen zu definieren (eine spezielle Form von Funktionen mit Seiteneffekten).

Um dieses Konzept zu verdeutlichen, sehen wir uns folgendes Beispiel an. Dieses Programm führt eine einfache Addition zweier Zahlen durch:

(+ 41 1)

Die runden Klammern umschließen die Elemente der Liste. Das + an erster Stelle ist ein so genanntes Symbol, 41 und 1 sind Zahlen. Symbole und Zahlen werden auch als Skalare (engl. scalars) bezeichnet. Clojure bietet eine Menge an Datentypen und dazugehörige Skalare beziehungsweise Literale (engl. literals): Integers, Floats, rationale Zahlen, Strings, Booleans, reguläre Ausdrücke, Collections etc. Die obige Codezeile stellt einen Funktionsaufruf dar. Zur Laufzeit wird dabei das erste Element der Liste (das Symbol) in einen Funktionsaufruf aufgelöst und die restlichen Elemente der Liste werden als Argumente dieser Funktion mitgegeben. Die Reihenfolge der Listenelemente ist von der Laufzeitumgebung vorgeschrieben. Man spricht dabei von so genannten Forms. Diese können als Verträge über den Aufbau von Listen zwischen dem Code und der Laufzeitumgebung gesehen werden:

(= 42 (+ 41 1))

Listenausdrücke können auch in verschachtelter Form angegeben werden, wie in dem letzten Beispiel zu sehen ist. Da Clojure-Code rein aus Listenausdrücken besteht, ist oft die Rede von Homoikonizität oder auch Selbstabbildung der Sprache; die Fähigkeit, sich durch eigene Datenstrukturen abzubilden und diese auch vom Programm aus modifizieren zu können.

Die Syntax ist auf den ersten Blick ungewöhnlich. Clojure bietet jedoch im Vergleich zu einer prozeduralen oder objektorientierten Programmiersprache einige Vorteile. Einer davon ist, dass die Anwendung der meisten Funktionen keine Zustandsänderung (engl. stateless) herbeiführt, sondern neue Objekte erzeugt. Somit findet auch keine Zustandsmanipulation von globalen Variablen oder Objekten statt. Selbst bei Collection-Datenstrukturen findet man keine Manipulation von Zuständen. Collections sind unveränderbar (engl. immutable) und persistent. Dies bedeutet, dass beispielsweise beim Hinzufügen eines Elements in ein Set dieses selbst nicht geändert, sondern ein neues Set auf Basis der älteren Version erzeugt wird. Persistente Collections können diese Eigenschaft performant und speicherschonend umsetzen. Die Unveränderbarkeit von Objekten ist eine wichtige Eigenschaft der nebenläufigen Programmierung.

Die Reduktion auf Listen, Symbole, Zahlen, andere Basisdatentypen und die grundlegenden Konzepte wie Unveränderbarkeit (engl. immutability) und starke funktionale Orientierung führen dazu, dass die Syntax und die grundlegenden Konzepte von Clojure relativ schnell erlernt werden können.

Listing 1.1 zeigt den syntaktischen Aufbau der Programmiersprache in einer ANTLR-Grammatik. Die Länge der Grammatik beträgt inklusive Leerzeilen 84 Zeilen (im Vergleich zu den in etwa 1 000 Zeilen von Java 8) und kann mit etwas Know-how über die BNF oder Ähnlichem gut gelesen werden.

grammar Clojure;

file: list*;

form: literal
| list
| Vektor
| map
| reader_macro
| '#\'' SYMBOL // TJP added (get Var object instead of the value of a
// symbol)
;

list: '(' form* ')' ;

Vektor: '[' form* ']' ;

map: '{' (form form)* '}' ;

// TJP added '&' (gather a variable number of arguments)
special_form: ('\'' | '`' | '~' | '~@' | '^' | '@' | '&') form ;

lambda: '#(' form* ')' ;

meta_data: '#^' map form ;

var_quote: '\'' '#' SYMBOL ;

regex: '#' STRING ;

reader_macro
: lambda
| meta_data
| special_form
| regex
| var_quote
| SYMBOL '#' // TJP added (auto-gensym)
;

literal
: STRING
| NUMBER
| CHARACTER
| NIL
| BOOLEAN
| KEYWORD
| SYMBOL
| PARAM_NAME
;

STRING : '"' ( ~'"' | '\\' '"' )* '"' ;

NUMBER : '-'? [0-9]+ ('.' [0-9]+)? ([eE] '-'? [0-9]+)? ;

CHARACTER : '\\' . ;

NIL : 'nil';

BOOLEAN : 'true' | 'false' ;

KEYWORD : ':' SYMBOL ;

SYMBOL: '.' | '/' | NAME ('/' NAME)? ;

PARAM_NAME: '%' (('1'..'9')('0'..'9')*)? ;

fragment
NAME: SYMBOL_HEAD SYMBOL_REST* (':' SYMBOL_REST+)* ;

fragment
SYMBOL_HEAD
: 'a'..'z' | 'A'..'Z' | '*' | '+' | '!' | '-' | '_' | '?' | '>' | '<' | '=' | '$'
;

fragment
SYMBOL_REST
: SYMBOL_HEAD
| '&' // apparently this is legal in an ID: "(defn- assoc-&-binding ..." TJP
| '0'..'9'
| '.'
;

WS : [ \n\r\t\,] -> channel(HIDDEN) ;

COMMENT : ';' ~[\r\n]* -> channel(HIDDEN) ;

Listing 1.1

Mit diesem notwendigen Basiswissen ausgestattet, können wir nun einen Blick auf die neuesten Entwicklungen in der Clojure-Welt werfen.

1.5 und 1.6

Die aktuell stabile Version von Clojure ist 1.6.0 und wurde Ende März 2014 veröffentlicht. Sie kann über einen Download [1] oder den Build-Tool/Package-Manager der Wahl bezogen werden. Die Entwicklung von Clojure ist auf GitHub nachzuvollziehen. Der Quellcode selbst steht unter einer Variante der Eclipse Public License. Für die hauptsächliche Weiterentwicklung von Clojure zeichnet die amerikanische Firma Cognitect [2] verantwortlich. Ein Teil des Teams ist mit Rich Hickey auch jener Programmierer, der die erste Version von Clojure im Alleingang über einen Zeitraum von ein paar Ja...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang