Review: ein Scheme-Weenie in Clojure-Land

Alles, was nicht direkt mit Python-Problemen zu tun hat. Dies ist auch der perfekte Platz für Jobangebote.
Antworten
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Servus!

Es ist wieder Winter, Zeit zum Programmieren.. ähm, naja, oder zumindest ist meine Motivation groß genug mich dazu aufzuraffen. Nachdem ich in Ein kurzer Ausflug in Scala mir Scala angeschaut habe wäre es langsam Zeit für ein neues Review.

Aber erstmal vielleicht noch ein Follow-Up zu Scala und den angesprochenen Themen: ich habe Scala seitdem nur halbherzig verfolgt. Es ist Version 2.8 rausgekommen, was quasi ein Meilenstein für die Scala-Leute ist. Die Sprache scheint sich gut zu entwickeln und das Projekt was ich in Scala geschrieben habe ist auch ganz ok verlaufen. Jedoch finde ich Scala trotz aller beteuerungen doch recht komplex. Ich hab ständig irgendwelche Sonder"syntax" wenn ich die Sprache effizient nutzen will. Ich kann natürlich auch Java in Scala schreiben. Was noch passiert ist: CLDC ist für mich tot, da ich kein Featurephone mehr nutze sondern auf ein Smartphone mit Android umgestiegen bin. Da läuft Dalvik drauf, was interessant ist weil man JVM-Bytecode dadrauf übersetzen kann. Das funktioniert mit Scala sogar ziemlich gut, habe mal zum testen ein Widget geschrieben. Das schwerste ist und war, ein brauchbares Icon zu finden. Daher nicht im Market 8)

Aber nun zu Clojure. Wie kam ich drauf? Ich habe versucht ein relativ simples Programm zu schreiben um Log-Dateien auszuwerten. Dazu braucht man, aus Python-Sicht eigentlich nichts weiter als ein paar Regular-Expressions und ein Dict. Also dachte ich mir ok, probier ich es mit Desktop-JavaScript. Dann hat mich aber der furchtbare Stand der GLib-Bindings abgestoßen. Ein zweiter Versuch mit Scheme führte etwas weiter, ist aber an Bugs in der Regex-Engine gescheitert. Dann war ich frustriert und hatte keine Lust mehr; hab mir dann spaßeshalber mal Clojure angeschaut.

Und hey, es hat tatsächlich Spaß gemacht. Es war einfach ne Implementation zu bekommen (gibt schließlich primär die auf der JVM) und das Buch hatte ich eh schon im Regal (direkt neben dem Scala-Buch). Also hab ich mal losprogrammiert und siehe da, immutable Datenstrukturen, wunderbar (und im Gegensatz zu Scheme auch brauchbar und ausreichend angenehm nutzbar um sie tatsächlich einzusetzen)! Die ganzen funktionalen Sachen wie ``comp``, ``partial``, ``map``, ``reduce`` sind da, weitere Goodies wie ``separate`` sind in Clojure-contrib. Clojure-contrib ist eh noch so ein eigenes Ding, denn ohne Clojure-contrib macht Clojure viel weniger Spaß, denn man müsste ständig Sachen wie etwa simple Datei-IO über Java-APIs machen oder irgendwelche Hilfsfunktionen selbst implementieren.

Achja, Java. Java fühlt sich in Clojure viel abgeschirmter als in Scala an. In Scala kann man auf Java-Code zugreifen fast wie auf Scala-Objekte, in Clojure muss man Java-Sachen immer etwas besonders ansprechen. Aber das ist auch kein Wunder, denn im Gegensatz zu Scala ist Clojure nicht objektorientiert, sondern nutzt Common Lisp-artige Multimethods. Interessantes Thema, aber habe mich noch nicht sonderlich damit auseinandergesetzt. Was mir Positiv aufgefallen ist, ist die Seq-Abstraktion, was in Python ein wenig dem Sequence-Protokoll entspricht. Die "Kern"-Datentypen lassen sich alle einheitlich ansprechen. Dann gibt es noch so einige nette Features, dass etwa :keywords auch als First-Class-Funktionen durchgehen und ich mit ihren auf Dicts zugreifen kann, ebenfalls dass Dicts wie Funktionen aufgerufen werden können. Insgesamt sind diese Details echt angenehm, man merkt dass sich jemand Gedanken gemacht hat, wie man sich das Leben angenehmer macht.

Was mir gefällt ist die Clojure Mailingliste und #clojure, dort habe ich oftmals gute Antworten bekommen. Ich frage gerne nach Codereviews und habe da auch schon einiges mitnehmen können. Oftmals ist es einfach irgendeine Funktion, die man nicht kannte, die aber die Aufgabe die man zu lösen versucht elegant löst. Und hier kommen wir etwas zu einem Problem. Clojure hat, womöglich Python-Inspiriert Docstrings und man kann diese mittels ``(doc foobar)`` lesen, aber die dort enthaltene Doku ist oft eher mager oder schwer zu verstehen. Ich habe mir beispielsweise mit ``reduce`` schwergetan, weil ich nicht realisiert habe dass es sich mit Startwert bei einer einelementigen Seq anders verhält (nämlich genau so wie ich brauche) als ohne. Die offizielle Dokumentation ist online allerdings muss ich sagen dass ich ClojureDocs wesentlich besser finde. Was aber auffällt dass es in Clojure-contrib einfach Sachen doppelt gibt (``separate`` ist in ``clojure-contrib.seq`` und ``clojure-contrib.seq-utils``) und einige Sachen schon jetzt deprecated sind. Dabei ist das alles noch nicht so alt... Wo wir gerade bei Dokumentation sind: Full Disclojure ist eine sehr interessante Serie wo in kurzen Episoden Clojure-Features vorgestellt werden, die die etwas "komplexeren" Features beleuchten. Man lernt vielleicht nicht sofort wie man es effizient nutzen kann, aber man weiß zumindest schon mal was es gibt und was man nutzen kann.

Ein weiteres Steckenpferd von Clojure ist Concurrency. Aber ich habe damit recht wenig zu tun, daher kann ich nur sagen dass ich mir das STM-Kapitel in "Programming Clojure" durchgelesen habe, genickt, und dann weitergeblättert habe. Mir kommt es etwas simplistisch vor, aber ohne ein Projekt damit realisiert zu haben ist meine Aussage da wenig Wert. Die Makros habe ich mir nicht angeschaut, aber als Fan von syntax-rules werde ich wohl von dem Common-Lisp artigen System wohl nur mäßig begeistert sein. Die sollen da ein System haben was Variablencapture in Makros verhindern soll, bin mal gespannt. Clojure hat auch Reader-Makros, allerdings sind die fest vorgegeben und damit eher Syntax. Kann man, genauso wie die [] und {} gut oder schlecht finden. Ich finds eher mäßig berauschend, aber es ist keine Katastrophe.

Außerdem hat Clojure wie Scala mit ``sbt`` ein eigenes Build-Tool, ``leiningen`` (nach einer Erzählung namens "Leiningen Versus the Ants" benannt, was ein witziges Wortspiel ist). Es erinnert wie ich finde auch weniger an ``ant`` sondern mehr wie ``maven`` oder eben ``sbt``. Wenn man sich an die Standardstruktur hält dann funktioniert es hinreichend gut (ich hab zwar gleich mal einen Bug gefunden, wurde aber innerhalb kürzester Zeit - während der Weihnachtsfeiertage - gepatcht) und kann auch so Standardsachen wie JARs von Clojars runterladen und Zeug paketieren. Das wirkt auf mich etwas aufgeräumter als ``pip`` und ``virtualenv``, gibt sich aber wohl nicht viel.

Was mir nicht gefallen hat sind die JVM-Startupzeiten. Mein Skript läuft innerhalb kürzester Zeit durch (was für Clojure spricht), aber von der Gesamtzeit macht das Booten der JVM den Großteil aus. Zudem unter amd64 nur die Server-VM verfügbar ist und die braucht mehr Zeit als die Client-VM. Was auch schade ist, ist dass GCJ das nicht kompilieren kann - da ist Common Lisp derzeit was native Compiler angeht besser aufgestellt. Aber vielleicht sollte ich micht als Python-User nicht so anstellen. Ich habe auch Ansätze wie Nailgun probiert, eine Art die JVM persistent laufen zu lassen und da den Code einfach immer nachzuladen, aber wirklich gut funktioniert das nicht. Gerade da hätte ich mir echt Verbesserung gewünscht, denn nachdem ich die Startup-Geschwindigkeit von Python gewohnt bin oder das Hintergrundkompilieren durch ``omake`` fühlt sich Code ausprobieren, Tests laufenlassen etc. recht schwerfällig an. Scala ist da zugegebenermaßen noch schlechter aufgestellt, denn ``scalac`` ist keine Geschwindigkeitsbestie.

Wiederrum positiv finde ich, dass Clojure ein brauchbares vim-Plugin hat, das out-of-the-Box halbwegs gut einrückt und highlightet. Eine Integration wie Slime hat vim nicht, zumindest wenn man nicht Nailgut nutzt. Aber Nailgun funktioniert bei mir nicht überzeugend.

Oh, nun ist es mehr geworden als ich dachte. tl;dr: Clojure gefällt mir entgegen Lisper-Vorurteilen besser als erwartet.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Danke :) Das hat mir Lust gemacht es wieder zu versuchen.

Beim letzten Mal hat mich der REPL leider ziemlich enttaeuscht (kein readline Support etc) und die Loesungen dafuer waren recht unschoen.
Hat sich das verbessert? Kannst du da was empfehlen?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

cofi hat geschrieben:Beim letzten Mal hat mich der REPL leider ziemlich enttaeuscht (kein readline Support etc) und die Loesungen dafuer waren recht unschoen.
Hat sich das verbessert? Kannst du da was empfehlen?
Ja. Arch Linux hat ein praktisches clj-Launcher-Script, es nutzt je nach Verfügbarkeit ``JLine`` oder ``rlwrap``. Ich nutze ``rlwrap`` (auch für andere Sachen wie ``guile`` oder ``ocaml``), sodass ich readline-Support habe, mit Klammer-Matching und es merkt sich die History über mehrere Sessions.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Danke das funktioniert gut :)

An der Qualitaet und Portablitaet laesst sich allerdings noch schrauben... auf einem Debian-System fliegt einem das erstmal um die Ohren, wenn `CLOJURE_HOME` nicht gesetzt ist :roll:
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

cofi hat geschrieben:An der Qualitaet und Portablitaet laesst sich allerdings noch schrauben... auf einem Debian-System fliegt einem das erstmal um die Ohren, wenn `CLOJURE_HOME` nicht gesetzt ist :roll:
Ja, mag sein. Ist aber wohl auch nicht als portable Lösung gedacht. Wundert mich etwas, dass die Clojure-Leute nicht selbst so ein vergleichbares Skript anbieten.

Achja, noch ein paar Anmerkungen: ich hatte einige Vorbehalte gegen das fehlen von TCO, da mir diese ``recur``-Form nicht sonderlich zugesagt hat. Wenn man "Programming Clojure" anschaut, sieht man dass es da mehrere Lösungen gibt. Die erste und einfachste ist, einfach keine Rekursion zu verwenden. Durch die lazy sequences und die vielen bereits fertigen Funktionen hatte ich bisher nicht den Bedarf nach Rekursiven Funktionen. Eine ander Möglichkeit ist es, eben ``recur`` zu nutzen, was aber nur geht wenn die Funktion sich selbst aufruft. Schließlich hat Clojure noch einen Trampolining-Helfer, so dass man per Hand Trampolines aufbauen kann um den Stack zu schonen. Mir kommt das zwar etwas wie "doing your compilers job" vor, aber zugegeben, der Clojure-"Compiler" ist nicht besonders schlau.

Zu den Makros: Clojure achtet drauf dass man keine Variablen captured, aber man muss dennoch manuell oder semi-manuell gensyms erstellen. Das ist zwar besser als in Common Lisp, aber ganz überzeugt bin ich nicht. Was die letzten Tage rausgekommen ist, fand ich dann doch schicker: syntax-rules, welches ``syntax-rules`` und ``syntax-case`` nach Clojure portiert. Ich muss zugeben dass mir die Syntax da gleich besser gefällt :)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Schöner Bericht. Ich teile die Einschätzung, dass Scala zwar nett, aber auch irgendwie kompliziert ist. Als Scala neu war, hatte ich ein bisschen damit experimentiert und es war schmerzhaft, die richtige Syntax zu treffen und aus den zum Teil kryptischen Fehlermeldungen des Compilers zu erraten, was falsch war. Das ist inzwischen besser und ich habe mir sagen lassen, der Scala Support von Intellij IDEA sei zur Zeit der Beste von den drei großen IDEs, doch auch dieser beherrscht noch nicht, was ich bei Java für selbstverständlich halte: Eine Prüfung der Syntax direkt bei der Eingabe. Was mich dann endgültig verschreckt hat war mein fehlgeschlagener Versuch, die Klassenhierarchie der neuen Collections zu verstehen. Das ist so unglaublich kompliziert, dass ich mich frage, ob es das Ergebnis wert war.

Clojure ist da viel einfacher und auch damit hatte ich gespielt, als es neu war. Ich dachte eigentlich, den Emacs hatte ich mit dem Studium hinter mir gelassen und so quälte ich mich länger damit, auf dem Mac einen Emacs mit Slime und Clojure zum Laufen zu bringen und kämpfte länger mit den archaischen Tastenkombinationen, als das ich wirklich mit Clojure etwas programmieren konnte. Während ich eigentlich die Erweiterung von Lisp-()-Syntax um [] und {} ganz gut finde, störte mich doch der Einsatz von "#" für "wir haben noch mehr Syntax", speziell bei Sets und Funktionen.

Und Clojure erzeugt(e in der Version 1.0 und 1.1) definitiv noch schlechtere Fehlermeldungen als Scala und echte IDEs waren (oder sind) immer noch Mangelware. Kann ich inzwischen im Einzelschritt durch ein Clojure-Programm debuggen?

Das Clojure (wie die meisten Java-Anwendungen) im Vergleich zu Python relativ lange braucht, um zu starten, finde ich jetzt nicht so schlimm. Bei mir sagt `time java -jar clojure.jar -e "(+ 3 4)"` 1,2 Sekunden. Um ein bisschen zu experimentieren, starte ich einmal die REPL. Zusammen mit Slime startet auch nur einmal der Interpreter.

Mein Hauptproblem mit Clojure ist allerdings, dass ich nicht weiß, wozu ich's einsetzen sollte :) STM für Concurrency ist vielversprechende Idee, aber Server-Systeme nun in Clojure statt traditionell in Java zu schreiben, ist glaube ich nicht mehrheitsfähig. Und in Java kann ich mit einer entsprechenden Bibliothek und etwas Disziplin, die ich mit PMD und Checkstyle und so weiter prüfe, ähnliches erreichen. AFAIK kann man die STM-Bibliothek inzwischen auch in JRuby nutzen. Natürlich hilft es, eine Sprache zu haben, die standardmäßig unveränderliche Variablen hat, aber so schick ich Lisp auch finde, für mich liegt der Wert eher in der Erweiterung des Horizonts als darin, ein Werkzeug gefunden zu haben, mit dem ich mein nächstes Projekt doppelt so schnell realisieren kann.

Stefan
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

sma hat geschrieben:ich habe mir sagen lassen, der Scala Support von Intellij IDEA sei zur Zeit der Beste von den drei großen IDEs, doch auch dieser beherrscht noch nicht, was ich bei Java für selbstverständlich halte: Eine Prüfung der Syntax direkt bei der Eingabe.
Wir haben bei unserem Compiler im letzt NetBeans und IDEA verwendet. NetBeans ist bei mir immer nach bestimmter Zeit eingefroren, nachdem es seinen Heap irgendwann vollgeschrieben hat. Abhilfe war Neustart, ein Vergrößern des max. Heaps hat das etwas hinausgezögert. Weiß grad nicht was bei IDEA die Kritikpunkte genau waren.
sma hat geschrieben:störte mich doch der Einsatz von "#" für "wir haben noch mehr Syntax", speziell bei Sets und Funktionen.
``#(forms)`` finde ich für anonyme Funktionen gar nicht mal so schlecht, wenn man das mit ``(lambda (args) forms)`` vergleicht. Aber Syntaxerweiterungen sind immer etwas kritisch. Wobei man die Set und Regex-Literale nicht nutzen muss, wenn man nicht will. Man muss auch keine Separators zwischen Listenelementen und Key/Values reinsetzen.
sma hat geschrieben:Und Clojure erzeugt(e in der Version 1.0 und 1.1) definitiv noch schlechtere Fehlermeldungen als Scala und echte IDEs waren (oder sind) immer noch Mangelware. Kann ich inzwischen im Einzelschritt durch ein Clojure-Programm debuggen?
Also IDEs nutze ich nicht, kennst mich ja ;) (ich glaub das beste IDE-Plugin ist momentan Enclojure für NetBeans) Aber die Fehlermeldungen sind in 1.2 noch definitiv genausoschlecht wie davor schon - bei Fehlern gibts nen Java-Traceback. Absurderweise hat mich das bisher nicht gestört, zumindest im Vergleich zu ``ocamlc`` oder ``scalac``, die zwar Fehlermeldungen bieten, die aber teilweise so bizarr sind dass sie einem oftmals nicht helfen.
sma hat geschrieben:Das Clojure (wie die meisten Java-Anwendungen) im Vergleich zu Python relativ lange braucht, um zu starten, finde ich jetzt nicht so schlimm. Bei mir sagt `time java -jar clojure.jar -e "(+ 3 4)"` 1,2 Sekunden.
Bei mir ist das auf einem 32-Bit-System ähnlich viel, bei 64-Bit eher so 6-8 Sekunden. Und das stört ähnlich wie die SVN-Reaktionszeiten wenn man Git gewöhnt ist :)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

6-8 Sekunden ist übel. Die 1,2 Sekunden bei mir sind mit der 64-bit-Server-VM von Apple.

Das `#(+ %1 %2) ` ist kürzer als `(fn (x y) (+ x y))`, klar. Was mich aber mehr stört ist, dass "#" keine Systematik hat. Es aus macht einem String einen regulären Ausdruck. Nun gut. Es macht aus einer Map ein Set. Hm. Es macht aus einem Quote eine Variable. Hä? Es macht aus einer Liste eine anonyme Funktion in der auf einmal %, %&, %1 bis %9 Variablen sind. Uh? Und gefolgt von einem _ ist es eine Art Super-Kommentar. Das ist so völlig beliebig "wir haben noch mehr Syntax".

Stefan
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Was die REPL angeht kommt natuerlich nichts an SLIME vorbei. In Verbindung mit clojure allerdings schrecklich aufzusetzen. Um das anderen einfacher zu machen, da swank clojure weder mit der CVS noch mit der Debian Version von SLIME arbeitet, sondern nur mit der ELPA Version (von technomancy).
  • Emacs 24 Version von package.el noetig (damit man mehr als ein Repository nutzen kann) zb von hier: https://github.com/technomancy/package.el
  • Technomancy Repository hinzufügen: (add-to-list 'package-archives '("technomancy" . "http://repo.technomancy.us/emacs/"))
  • clojure-mode, slime und slime-repl installieren: (mapc 'package-install '("clojure-mode" "slime" "slime-repl"))
Damit ist Emacs vorbereitet, aber man hat immernoch kein brauchbares SLIME.
Enter leinigen: https://github.com/technomancy/leiningen
Damit kann man Projekte erstellen und gibt man in diesen Projekten `swank-clojure` als `dev-dependency` an, kann man mit `lein swank` (nach einem `lein deps`) einen Swank Server starten auf den man dann per `slime-connect` zugreifen kann.

Ein Spass fuer den ganzen Tag.
Als waere das noch nicht genug hat bei mir `lein deps` gestreikt und ich musste die noetigen Jars von Hand installieren und das Maven beibringen.

Zu `recur`: Dass die Forms zwar sequentiell ausgewertet werden aber erst danach erst wieder an die Variablen gebunden werden kann boese beissen und zu unerklaerlichen Ergebnissen fuehren, wenn man von einander ahängige Variablen hat :(
Antworten