it-swarm-eu.dev

Schema vs Common LISP: Welche Eigenschaften haben in Ihrem Projekt einen Unterschied gemacht?

Es gibt keinen Mangel an vagen "Scheme vs Common LISP" -Fragen sowohl bei StackOverflow als auch auf dieser Site, daher möchte ich diese Frage fokussierter gestalten. Die Frage richtet sich an Personen, die in beiden Sprachen codiert haben:

Welche spezifischen Elemente Ihrer Common LISP-Codierungserfahrung haben Sie beim Codieren in Scheme am meisten vermisst? Oder umgekehrt, was haben Sie beim Codieren in Common LISP beim Codieren in Scheme vermisst?

Ich meine nicht unbedingt nur Sprachfunktionen. Folgendes sind alle gültigen Dinge, die in Bezug auf die Frage zu übersehen sind:

  • Spezifische Bibliotheken.
  • Spezifische Funktionen von Entwicklungsumgebungen wie SLIME, DrRacket usw.
  • Funktionen bestimmter Implementierungen, z. B. die Fähigkeit von Gambit, C-Codeblöcke direkt in Ihre Schemaquelle zu schreiben.
  • Und natürlich Sprachfunktionen.

Beispiele für die Art von Antworten, auf die ich hoffe:

  • "Ich habe versucht, X in Common LISP zu implementieren, und wenn ich die erstklassigen Fortsetzungen von Scheme gehabt hätte, hätte ich total nur Y gemacht, aber stattdessen musste ich Z machen, was eher schmerzhaft war."
  • "Das Erstellen von Skripten für den Erstellungsprozess in meinem Schema-Projekt wurde immer schmerzhafter, als mein Quellbaum wuchs und ich immer mehr C-Bibliotheken verknüpfte. Für mein nächstes Projekt wechselte ich zurück zu Common LISP."
  • "Ich habe eine große vorhandene C++ - Codebasis, und für mich war die Möglichkeit, C++ - Aufrufe direkt in meinen Gambit-Schema-Code einzubetten, alle Mängel wert, die das Schema gegenüber Common LISP haben könnte, selbst wenn es keine SWIG-Unterstützung gibt."

Ich hoffe also eher auf Kriegsgeschichten als auf allgemeine Gefühle wie "Schema ist eine einfachere Sprache" usw.

159
SuperElectric

Mein Bachelor-Abschluss war in Kognitionswissenschaft und Künstlicher Intelligenz. Von da an hatte ich ein Ein-Gänge-Intro zu LISP. Ich fand die Sprache interessant (wie in "elegant"), dachte aber nicht viel darüber nach, bis ich viel später auf Greenspuns Zehnte Regel stieß:

Jedes ausreichend komplizierte C- oder Fortran-Programm enthält eine ad hoc, informell spezifizierte, fehlerbehaftete, langsame Implementierung der Hälfte von Common LISP.

Greenspuns Argument war (teilweise), dass viele komplexe Programme eingebaute Dolmetscher haben. Anstatt einen Interpreter in eine Sprache zu integrieren, schlug er vor, dass es sinnvoller sein könnte, eine Sprache wie LISP zu verwenden, in die bereits ein Interpreter (oder Compiler) integriert ist.

Zu der Zeit hatte ich an einer ziemlich großen App gearbeitet, die benutzerdefinierte Berechnungen mit einem benutzerdefinierten Interpreter für eine benutzerdefinierte Sprache durchführte. Ich beschloss, seinen Kern in LISP als großangelegtes Experiment neu zu schreiben.

Es dauerte ungefähr sechs Wochen. Der ursprüngliche Code war ~ 100.000 Zeilen Delphi (eine Pascal-Variante). In LISP wurde das auf ~ 10.000 Zeilen reduziert. Noch überraschender war jedoch die Tatsache, dass die LISP-Engine 3-6 mal schneller war. Und denken Sie daran, dass dies die Arbeit eines LISP-Neulings war! Diese ganze Erfahrung hat mir die Augen geöffnet. Zum ersten Mal sah ich die Möglichkeit, Leistung und Ausdruckskraft in einer einzigen Sprache zu kombinieren.

Einige Zeit später, als ich anfing, an einem webbasierten Projekt zu arbeiten, sprach ich eine Reihe von Sprachen vor. Ich habe LISP und Scheme in den Mix aufgenommen. Am Ende habe ich eine Schema-Implementierung ausgewählt -- Chez-Schema . Ich war sehr zufrieden mit den Ergebnissen.

Das webbasierte Projekt ist eine leistungsstarke "Auswahl-Engine" . Wir verwenden Scheme auf verschiedene Arten, von der Datenverarbeitung über die Datenabfrage bis zur Seitengenerierung. An vielen Stellen haben wir tatsächlich mit einer anderen Sprache angefangen, sind aber aus Gründen, die ich im Folgenden kurz beschreiben werde, zu Scheme gewandert.

Jetzt kann ich Ihre Frage (zumindest teilweise) beantworten.

Während des Vorspiels haben wir uns verschiedene LISP- und Schema-Implementierungen angesehen. Auf der LISP-Seite haben wir uns (glaube ich) Allegro CL, CMUCL, SBCL und LispWorks angesehen. Auf der Schemaseite haben wir uns (glaube ich) Bigloo, Chicken, Chez, Gambit angesehen. (Die Sprachauswahl ist lange her; deshalb bin ich ein bisschen verschwommen. Ich kann ein paar Notizen ausgraben, wenn es wichtig ist.)

Von Anfang an suchten wir nach a) nativen Threads und b) Linux-, Mac- und Windows-Unterstützung. Diese beiden Bedingungen zusammen haben alle außer (ich denke) Allegro und Chez umgehauen. Um die Bewertung fortzusetzen, mussten wir die Multithreading-Anforderung lockern.

Wir haben eine Reihe kleiner Programme zusammengestellt und diese zur Bewertung und zum Testen verwendet. Das ergab eine Reihe von Problemen. Beispiel: Einige Implementierungen hatten Fehler, die verhinderten, dass einige Tests vollständig ausgeführt wurden. Einige Implementierungen konnten zur Laufzeit keinen Code kompilieren. Einige Implementierungen konnten zur Laufzeit kompilierten Code nicht einfach in vorkompilierten Code integrieren. Einige Implementierungen hatten Müllsammler, die eindeutig besser (oder deutlich schlechter) waren als die anderen. usw.

Für unsere Bedürfnisse haben nur die drei kommerziellen Implementierungen - Allegro, Chez und Lispworks - unsere primären Tests bestanden. Von den dreien bestand nur Chez alle Tests mit Bravour. Zu der Zeit hatte Lispworks auf keiner Plattform native Threads (ich glaube, das tun sie jetzt), und ich denke, Allegro hatte auf einigen Plattformen nur native Threads. Außerdem hatte Allegro eine Laufzeit-Lizenzgebühr, die mir nicht besonders gut gefallen hat. Ich glaube, Lispworks hatte keine Laufzeitgebühr und Chez hatte eine unkomplizierte (und sehr vernünftige) Vereinbarung (und sie trat nur ein, wenn Sie den Compiler zur Laufzeit verwendeten).

Nachdem sowohl in LISP als auch in Scheme etwas signifikante Codestücke erzeugt wurden, sind hier einige Vergleichs- und Kontrastpunkte aufgeführt:

  • Die LISP-Umgebungen sind weitaus ausgereifter. Sie bekommen viel mehr für das Geld. (Allerdings bedeutet mehr Code auch mehr Fehler.)

  • Die LISP-Umgebungen sind weitaus schwieriger zu erlernen. Sie brauchen viel mehr Zeit, um kompetent zu werden. Common LISP ist eine riesige Sprache - und das ist, bevor Sie zu den Bibliotheken gelangen, die die kommerziellen Implementierungen zusätzlich hinzufügen. (Trotzdem ist Schemas Syntax-Fall weitaus subtiler und komplizierter als alles in LISP.)

  • In den LISP-Umgebungen kann es etwas schwieriger sein, Binärdateien zu erstellen. Sie müssen Ihr Image "schütteln", um nicht benötigte Bits zu entfernen. Wenn Sie Ihr Programm während dieses Vorgangs nicht richtig ausführen, können später Laufzeitfehler auftreten . Im Gegensatz dazu kompilieren wir mit Chez eine Datei der obersten Ebene, die alle anderen benötigten Dateien enthält, und wir sind fertig.

Ich sagte zuvor, dass wir Scheme an einer Reihe von Orten verwendet haben, die wir ursprünglich nicht beabsichtigt hatten. Warum? Ich kann mir drei Gründe vorstellen.

Zuerst haben wir gelernt, Chez (und seinem Entwickler Cadence) zu vertrauen. Wir haben viel vom Tool verlangt und es wurde konsequent geliefert. Zum Beispiel hatte Chez in der Vergangenheit eine trivial kleine Anzahl von Fehlern, und sein Speichermanager war sehr, sehr gut.

Zweitens haben wir gelernt, die Leistung von Chez zu lieben. Wir haben etwas verwendet, das sich wie eine Skriptsprache anfühlte - und wir haben die Geschwindigkeit des nativen Codes erhalten. Für einige Dinge, die keine Rolle spielten - aber es tat nie weh, und manchmal half es sehr viel.

Drittens haben wir gelernt, das Abstraktionsschema zu lieben, das es bieten kann. Ich meine übrigens nicht nur Makros; Ich meine Dinge wie Schließungen, Lambdas, Tail-Calls usw. Sobald Sie anfangen, in diesen Begriffen zu denken, scheinen andere Sprachen im Vergleich eher eingeschränkt zu sein.

Ist das Schema perfekt? Nein; Es ist ein Kompromiss. Erstens können einzelne Entwickler effektiver arbeiten - für Entwickler ist es jedoch schwieriger, den Code des anderen zu erfassen, da die Wegweiser der meisten Sprachen (z. B. für Schleifen) im Schema fehlen (z. B. gibt es eine Million Möglichkeiten eine for-Schleife). Zweitens gibt es einen viel kleineren Pool von Entwicklern, mit denen man sprechen, einstellen, ausleihen usw. kann.

Zusammenfassend würde ich sagen: LISP und Scheme bieten einige Funktionen, die nirgendwo anders verfügbar sind. Diese Fähigkeit ist ein Kompromiss, daher sollte sie in Ihrem speziellen Fall sinnvoll sein. In unserem Fall hatten die entscheidenden Faktoren zwischen LISP oder Scheme mehr mit sehr grundlegenden Funktionen (Plattformunterstützung, Plattformthreads, Laufzeitkompilierung, Laufzeitlizenzierung) zu tun als mit Sprach- oder Bibliotheksfunktionen. Auch in unserem Fall war dies ein Kompromiss: Mit Chez haben wir die Kernfunktionen erhalten, die wir wollten, aber wir haben die umfangreichen Bibliotheken verloren, die die kommerziellen LISP-Umgebungen hatten.

Um es noch einmal zu wiederholen: Wir haben uns vor langer Zeit die verschiedenen Lisps und Schemes angesehen. Seitdem haben sie sich alle weiterentwickelt und verbessert.

102

Normalerweise mag ich es nicht, einen Link als Antwort einzufügen, aber ich habe einen Blog-Artikel darüber geschrieben. Es ist nicht erschöpfend, aber es bringt einige der wichtigsten Punkte durch.

http://symbo1ics.com/blog/?p=729

Bearbeiten: Hier sind die wichtigsten Punkte:

  1. [~ # ~] Existenz [~ # ~] : Beide Lisps kamen nach einer Reihe anderer Lisps. Das Schema nahm den minimalen, axiomatischen Weg. CL nahm den Barockweg.
  2. [~ # ~] case [~ # ~] : Normalerweise unterscheidet das Schema zwischen Groß- und Kleinschreibung. CL ist nicht (obwohl es sein kann). Dies wird manchmal übersehen, aber seine Praktikabilität wird (von mir) diskutiert.
  3. [~ # ~] Namen [~ # ~] : Namen von Symbolen in CL sind oft ungerade und verwirrend. TERPRI, PROGN usw. Das Schema hat normalerweise sehr vernünftige Namen. Dies wird in CL übersehen.
  4. [~ # ~] Funktionen [~ # ~] : CL hat einen separaten Funktionsnamensraum. Dies ist nicht im Schema übersehen. Ein einziger Namespace ermöglicht normalerweise eine sehr saubere funktionale Programmierung, was in CL oft schwierig oder umständlich ist. Aber es hat seinen Preis - manchmal müssen Sie Namen wie "list" bis "lst" im Schema verschleiern.
  5. [~ # ~] Makros [~ # ~] : Ich vermisse schmutzige Makros auf niedriger Ebene am meisten in Schema. Ja, syntax-rules ist alles in Ordnung und gut, bis Sie einige Dinge wirklich heraushacken wollen. Andererseits werden Hygienemakros in CL manchmal übersehen. Wenn Sie keine Standardmethode haben, müssen Sie das Rad neu erfinden.
  6. [~ # ~] Portabilität [~ # ~] : CL ist häufig portabler, obwohl beide Sprachen standardisiert sind. CL ist größer und daher gibt es mehr Standardfunktionen, die ohne externe Bibliotheken verwendet werden können. Dies bedeutet auch, dass mehr implementierungsabhängige Dinge portabel erledigt werden können. Außerdem leidet Scheme unter einer Billion Implementierungen, von denen die meisten etwas inkompatibel sind. Dies macht CL sehr wünschenswert.
  7. [~ # ~] Bibliotheken [~ # ~] : Sehr verwandt mit meinem letzten Punkt. Das Programm hat SRFIs, ist jedoch nicht allgemein anerkannt. Es gibt keine tragbare Möglichkeit, mit Bibliotheken zu arbeiten. CL dagegen hat Wege. Und Quicklisp ist ein Geschenk Gottes (Xach) - eine Art Aufbewahrungsort für Bibliotheken.
  8. [~ # ~] Implementierungen [~ # ~] : Das Schema leidet unter so vielen Implementierungen. Es gibt keine wirkliche kanonische Umsetzung. CL hingegen verfügt über einige sehr schöne Implementierungen mit hoher Leistung oder spezifischer Verwendung (hohe Leistung: SBCL, kommerziell: Allegro, eingebettet: ECL, portabel: CLISP, Java: ABCL, ...).

Während ich oben nur in der ersten Person gesprochen habe, sollte klar sein, was ich vermisse und was nicht.

[Ich entschuldige mich, wenn diese zu allgemein sind. Es scheint, dass Sie viel spezifischere Details wünschen. Es gibt einige Besonderheiten in der Post.]

37
Quadrescence

Ich habe kürzlich ein Heimprojekt mit einer Bibliothek gestartet, die eine C-Version und eine Java -Version) hat. Ich wollte LISP für das Projekt verwenden und habe ungefähr einen Monat zwischen der Verwendung von Common LISP, Scheme, geschwankt oder Clojure. Ich habe einige Erfahrungen mit allen drei, aber nur mit Spielzeugprojekten. Ich werde Ihnen ein wenig über meine Erfahrungen mit jedem von ihnen erzählen, bevor ich Ihnen sage, welches ich letztendlich ausgewählt habe.

PLT Racket hat ein Nice IDE, mit dem Sie nicht nur Ausdrücke aus dem Editor auswerten, sondern auch Klammern anstelle von Parens eingeben und gegebenenfalls wieder auf Parens zurücksetzen können. Racket hat auch einen großen Satz von Bibliotheken mit der Installation und noch mehr zum Download verfügbar. Der visuelle Debugger ist auch hilfreich.

Meine Common LISP-Implementierung (SBCL) hat keine IDE, aber es ist bei Open-Source-CL-Implementierungen üblich, Emacs und SLIME zu verwenden. Diese Kombination kann sehr effizient sein. Neben der Möglichkeit, Ausdrücke beim Eingeben in die Quelldatei auszuwerten, gibt es auch ein REPL, das alle Bearbeitungsbefehle von Emacs zur Verfügung hat, sodass das Kopieren von Code in beide Richtungen effizient erfolgen kann Objekte, die im Puffer REPL) angezeigt werden, können kopiert und eingefügt werden. Alt+( und Alt+) sind effizient für den Umgang mit übereinstimmenden Klammern und Einrückungen.

Alle oben genannten Emacs-Funktionen sind auch für Clojure verfügbar. Meine Bearbeitungserfahrung mit Clojure ähnelt der von LISP. Das Interop Java] hat einwandfrei funktioniert, und ich möchte ein Clojure-Projekt durchführen, sobald es ausgereift ist.

Ich konnte mit allen drei (Common LISP, Racket und Clojure) auf die Bibliothek zugreifen, entschied mich jedoch für Common LISP für das Projekt. Der entscheidende Faktor war, dass der FFI in Common LISP viel einfacher zu verwenden war. CFFI hat ein sehr gutes Handbuch mit Beispielcode und detaillierten Erklärungen zu jeder Methode. Ich konnte an einem Nachmittag 20 C-Funktionen einpacken und musste den Code seitdem nicht mehr berühren.

Der andere Faktor war, dass ich mit Common LISP besser vertraut bin als mit Clojure oder R6RS Scheme. Ich habe die meisten Bücher von Practical Common LISP und Graham gelesen und bin mit der Hyperspec zufrieden. Es ist noch kein sehr "lispy" Code, aber ich bin sicher, dass sich das ändern wird, wenn ich mehr Erfahrung sammle.

25
Larry Coleman

Ich programmiere sowohl in CL als auch in Racket.

Ich entwickle gerade eine Website in Common LISP und habe eine Reihe von internen Programmen für meinen früheren Arbeitgeber in Racket geschrieben.

Für den internen Code habe ich Racket (damals als PLT-Schema bekannt) gewählt, weil der Arbeitgeber ein Windows-Shop war und ich sie nicht dazu bringen konnte, für LispWorks zu bezahlen. Die einzige gute Open-Source-CL-Implementierung für Windows war (und ist) CCL, die SSE Unterstützung im Prozessor) erfordert. Der Arbeitgeber, der billig ist, verwendete Steinzeit-Hardware. Selbst wenn der Arbeitgeber über anständige Hardware verfügte, ist McCLIM die einzige GUI-Bibliothek mit Konsequenz in Common LISP, die nur unter Unix funktioniert. Racket verfügt über eine gute GUI-Bibliothek, die sowohl unter Unix als auch unter Windows funktioniert, was für mich von entscheidender Bedeutung war Erfolg des Projekts.

Ich habe über ein Jahr damit verbracht, mich mit dem primitiven DrRacket-Editor abzufinden. EMACS konnte die GUI-Version von Racket, damals als MrEd bekannt, unter Windows nicht in ein minderwertiges LISP verwandeln. Ich musste darauf verzichten, den Ausdruck am Cursor mit einem einzigen Tastendruck auswerten zu können. Stattdessen musste ich den S-Ausdruck manuell auswählen, kopieren, auf das Fenster REPL (da kein Tastendruck zum Umschalten vorhanden ist) klicken und dann den S-Ausdruck einfügen musste auf einen Editor verzichten, der mir die erwarteten Argumente der von mir verwendeten Funktion oder des verwendeten Makros anzeigen konnte. DrRacket ist kein Ersatz für SLIME.

Der Arbeitgeber verwendete eine proprietäre Datenbank mit einer komplizierten XML-API, für die eine Menge scheinbar unnötiger Informationen erforderlich waren, um auf die Version einer SELECT-Abfrage antworten zu können. Ich habe mich entschieden, HTMLPrag zu verwenden, um XML an diese API zu senden und die Antworten zu analysieren. Es hat super funktioniert.

Ich musste Rackets überkompliziertes "Syntax-Case" -Makrosystem lernen, um ein Makro zu schreiben, mit dem ich mit der überkomplizierten XML-API interagieren konnte, indem ich Formulare eingab, die wie SQL aussahen. Dieser Teil wäre viel einfacher gewesen, wenn ich DEFMACRO zur Verfügung gehabt hätte. Das Endergebnis war jedoch immer noch nahtlos, obwohl mehr Aufwand erforderlich war, um es zu erreichen.

Außerdem musste ich auf das LOOP-Makro von Common LISP verzichten. Racket begann erst mit der Bereitstellung einer Alternative, nachdem ich den größten Teil des Codes geschrieben hatte, und die Alternative ist im Vergleich zu LOOP immer noch schlecht (obwohl das Entwicklungsteam von Racket darauf besteht, dass es besser ist - sie liegen einfach falsch). Am Ende habe ich viele benannte LET-Formulare geschrieben, die "Auto" und "CDR" verwendeten, um Listen zu durchlaufen.

Apropos Auto und CDR: Nichts ist frustrierender als Schemes Interpretation von (car '()) als Fehler. Ich nutzte die Groß- und Kleinschreibung von Racket und implementierte CAR und CDR, die die Semantik von Common LISP haben. Die Trennung von '() und #f macht es jedoch weitaus weniger nützlich,' () als Standardwert zurückzugeben.

Am Ende habe ich auch UNWIND-PROTECT erneut implementiert und mein eigenes Neustartsystem erfunden, um die Lücke zu schließen, die Racket hinterlassen hat. Die Racket-Community muss lernen, dass Neustarts sehr nützlich und einfach zu implementieren sind.

Die Let-Values-Form von Racket war zu ausführlich, daher habe ich MULTIPLE-VALUE-BIND implementiert. Dies war absolut notwendig, da Racket erfordert Sie alle die Werte erhalten, die generiert werden, unabhängig davon, ob Sie sie verwenden oder nicht.

Später habe ich versucht, einen eBay XML API-Client in Common LISP zu schreiben, nur um festzustellen, dass er nichts wie HTMLPrag enthält. HTMLPrag ist verdammt nützlich. Am Ende habe ich dieses Projekt in Racket gemacht. Ich habe mit Rackets Literate Programming-Funktionen experimentiert, nur um herauszufinden, dass ich der einzige Programmierer auf der Welt bin, der es richtig findet, richtig geschriebenen Literate-Code zu bearbeiten als normalen Code oder falsch geschriebenen Literate-Code mit "übermäßigen Kommentaren".

Mein neues Projekt wird in Common LISP durchgeführt, was die richtige Wahl war, da die Racket-Community einfach nicht an Parallelität glaubt, was für dieses Projekt wesentlich ist. Das einzige, was ich von Racket vermisst haben könnte, waren Fortsetzungen. Durch Neustarts konnte ich jedoch das tun, was ich brauchte, und im Nachhinein hätte ich es wahrscheinlich mit einem einfachen Abschluss tun können.

21
Racketeer

Das Schema ist für eine separate Zusammenstellung konzipiert. Infolgedessen ist die Leistung seiner Makros häufig stark eingeschränkt, selbst bei den Erweiterungen, die ein Defmacro im Common LISP-Stil anstelle eines schlechten, einschränkenden hygienischen Makrosystems ermöglichen. Es ist nicht immer möglich, ein Makro zu definieren, das ein anderes Makro definiert, das für die sofortige Verwendung in einer nächsten Codezeile vorgesehen ist. Eine solche Möglichkeit ist für die Implementierung effizienter eDSL-Compiler unerlässlich.

Unnötig zu erwähnen, dass Schema-Implementierungen mit nur R5RS-Hygienemakros für mich kaum nützlich sind, da mein Metaprogrammierstil nicht angemessen auf Hygiene übertragen werden kann.

Glücklicherweise gibt es Schema-Implementierungen (z. B. Racket), die diese Einschränkung nicht aufweisen.

5
SK-logic