Nach einigen Monaten des Lernens und Spielens mit Lisp, sowohl CL als auch ein bisschen Clojure, sehe ich immer noch keinen zwingenden Grund, etwas statt C # darin zu schreiben.
Ich hätte gerne überzeugende Gründe oder möchte, dass jemand darauf hinweist, dass mir etwas wirklich Großes fehlt .
Die Stärken von Lisp (nach meiner Recherche):
- Kompakte, ausdrucksstarke Notation - Mehr als C #, ja ... aber ich scheine in der Lage zu sein, diese Ideen auch in C # auszudrücken.
- Implizite Unterstützung für funktionale Programmierung - C # mit LINQ-Erweiterungsmethoden:
- mapcar = .Select (Lambda)
- mapcan = .Select (Lambda) .Aggregate ((a, b) => a.Union (b))
- car / first = .First ()
- cdr / rest = .Skip (1) .... etc.
- Unterstützung für Lambda-Funktionen und Funktionen höherer Ordnung - C # hat dies und die Syntax ist wohl einfacher:
- (Lambda (x) (Körper)) gegen x => (Körper)
- "# (" mit "%", "% 1", "% 2" ist in Clojure nett
- Methodenversand von den Objekten getrennt - C # verfügt über Erweiterungsmethoden
- Multimethod Dispatch - C # hat dies nicht von Haus aus, aber ich könnte es in ein paar Stunden als Funktionsaufruf implementieren
- Code ist Daten (und Makros) - Vielleicht habe ich keine Makros "bekommen", aber ich habe kein einziges Beispiel gesehen, in dem die Idee eines Makros nicht als Funktion implementiert werden konnte. es ändert nichts an der "Sprache", aber ich bin mir nicht sicher, ob das eine Stärke ist
- DSLs - Kann es nur durch Funktionszusammenstellung tun ... aber es funktioniert
- Nicht typisierte "explorative" Programmierung - für Strukturen / Klassen funktionieren C # - Eigenschaf - ten und "Objekt" recht gut, und Sie können im Laufe der Zeit leicht zu einer stärkeren Typisierung eskalieren
- Läuft auf Nicht-Windows-Hardware. Außerhalb des Studiums kannte ich nur eine Person, die Windows nicht zu Hause ausführt, oder zumindest eine VM von Windows unter * nix / Mac. (Andererseits ist das vielleicht wichtiger als ich dachte und ich wurde gerade einer Gehirnwäsche unterzogen ...)
- Die REPL für Bottom-Up-Design - Ok, ich gebe zu, das ist wirklich sehr schön und ich vermisse es in C #.
Was mir in Lisp fehlt (aufgrund einer Mischung aus C #, .NET, Visual Studio und Resharper):
- Namespaces. Selbst mit statischen Methoden binde ich sie gerne an eine "Klasse", um ihren Kontext zu kategorisieren (Clojure scheint dies zu haben, CL scheint dies nicht zu tun).
- Hervorragende Unterstützung für Kompilierung und Design
- Das Typensystem ermöglicht es mir, die "Korrektheit" der Datenstrukturen zu bestimmen, die ich herumgebe
- Alles, was falsch geschrieben wurde, ist in Echtzeit unterstrichen. Ich muss nicht bis zur Laufzeit warten, um zu wissen
- Code-Verbesserungen (z. B. die Verwendung eines FP-Ansatzes anstelle eines Imperativ-Ansatzes) werden automatisch empfohlen
- GUI-Entwicklungstools: WinForms und WPF (Ich weiß, dass Clojure Zugriff auf die Java-GUI-Bibliotheken hat, aber sie sind mir völlig fremd.)
- GUI-Debugging-Tools: Haltepunkte, Step-In, Step-Over, Werteinspektoren (Text, XML, benutzerdefiniert), Überwachungen, Debug-by-Thread, bedingte Haltepunkte, Call-Stack-Fenster mit der Möglichkeit, auf jeder Ebene zum Code zu springen im Stapel
- (Um fair zu sein, mein Aufenthalt bei Emacs + Slime schien einiges davon zu liefern, aber ich bin ein Teil des VS GUI-gesteuerten Ansatzes.)
Ich mag den Hype um Lisp sehr und habe ihm eine Chance gegeben.
Aber kann ich in Lisp etwas tun, was ich in C # nicht so gut kann? Es mag in C # etwas ausführlicher sein, aber ich habe auch Autocomplete.
Was vermisse ich? Warum sollte ich Clojure / CL verwenden?
AND
oderOR
als Funktion. Es könnte getan werden (vorausgesetztLAMBDA
, das ist auch ein Makro), aber ich sehe keinen offensichtlichen Weg, um es zu tun, der nicht völlig zum Kotzen wäre.:
(oder::
für nicht exportierte Symbole) zum Benennen von Symbolen in Paketen, z. B. würde Ihr Beispiel hierhttp:session
und verwendenchat:session
.Antworten:
Mit Makros können Sie Compiler schreiben, ohne die Bequemlichkeit Ihrer Sprache zu verlieren. DSLs in Sprachen wie C # sind in der Regel ein Laufzeitinterpreter. Je nach Domain kann dies aus Performancegründen problematisch sein. Geschwindigkeit ist wichtig , sonst würden Sie in einer interpretierten Sprache und nicht in C # codieren.
Mit Makros können Sie auch menschliche Faktoren berücksichtigen - was ist die benutzerfreundlichste Syntax für ein bestimmtes DSL? Mit C # können Sie nicht viel an der Syntax ändern.
So können Sie mit Lisp am Sprachdesign teilnehmen, ohne den Weg zur Effizienz aufzugeben . Und während diese Probleme können nicht egal Sie , sind sie äußerst wichtig , da sie die Grundlage für alle nützlichen produktionsorientierten Programmiersprachen zu Grunde liegen. In C # wird Ihnen dieses grundlegende Element bestenfalls in sehr eingeschränkter Form angezeigt.
Die Bedeutung von Makros geht in anderen Sprachen nicht verloren - Template Haskell, camlp4. Aber auch hier ist es ein Problem der Benutzerfreundlichkeit : Bisherige Lisp-Makros sind wahrscheinlich die benutzerfreundlichste und leistungsfähigste Implementierung der Transformation zur Kompilierungszeit.
Zusammenfassend lässt sich sagen, dass Menschen im Allgemeinen mit Sprachen wie C # DSILs (Domain Specific Interpreted Languages) erstellen. Mit Lisp haben Sie die Möglichkeit, radikalere DSPs (Domain Specific Paradigms) zu erstellen. Racket (ehemals PLT-Scheme) ist in dieser Hinsicht besonders inspirierend: Es gibt eine faule Sprache (Lazy Racket), eine typisierte Sprache (Typed Racket), Prolog und Datalog, die alle über Makros idiomatisch eingebettet sind. All diese Paradigmen bieten leistungsstarke Lösungen für große Problemklassen - Probleme, die nicht am besten durch imperative Programmierung oder sogar FP-Paradigmen gelöst werden können.
quelle
Fast alles, was ursprünglich nur in Lisps verfügbar war, ist dankenswerterweise in viele andere moderne Sprachen migriert. Die größte Ausnahme bilden die Philosophie "Code ist Daten" und Makros.
Ja, Makros sind schwer zu finden. Metaprogrammierung im Allgemeinen ist schwer zu fassen. Wenn Sie ein Makro gesehen haben, das als Funktion implementiert werden kann, hat der Programmierer es falsch gemacht. Makros sollten nur verwendet werden, wenn eine Funktion nicht funktioniert.
Das "Hallo Welt" -Beispiel eines Makros ist das Schreiben von "if" in Bezug auf cond. Wenn Sie wirklich daran interessiert sind, die Leistungsfähigkeit von Makros zu erlernen, versuchen Sie, in clojure ein "my-if" zu definieren, indem Sie Funktionen verwenden und beobachten, wie es kaputt geht. Ich will nicht mysteriös sein, ich habe nur noch nie jemanden gesehen, der angefangen hat, Makros durch Erklärungen zu verstehen, aber diese Diashow ist ein ziemlich gutes Intro: http://www.slideshare.net/pcalcado/lisp-macros-in -20-Minuten-mit-Clojure-Präsentation
Ich hatte kleine Projekte, in denen ich buchstäblich Hunderte / Tausende von Codezeilen mit Makros gespeichert habe (verglichen mit dem, was es in Java gekostet hätte). Ein Großteil von Clojure ist in Clojure implementiert, was nur aufgrund des Makrosystems möglich ist.
quelle
(defun my-if (test then &optional else) (cond (test (funcall then)) ('otherwise (funcall else))))
Ich bin kein Experte für Common Lisp, aber ich kenne sowohl C # als auch Clojure einigermaßen gut. Hoffentlich ist dies eine nützliche Perspektive.
Aufgrund der unglaublich einfachen Syntax ist Lisps "Code is Data" -Philosophie viel leistungsfähiger als alles, was Sie mit Funktionskomposition / -vorlagen / -generika usw. tun können. Es ist mehr als nur DSLs, es ist die Codegenerierung und -manipulation zur Kompilierungszeit , als grundlegendes Merkmal der Sprache. Wenn Sie versuchen, diese Art von Funktionen in einer nicht homoikonischen Sprache wie C # oder Java zu erhalten, werden Sie Lisp schlimmstenfalls neu erfinden. Daher die berühmte Zehnte Regel der Greenspuns: "Jedes ausreichend komplizierte C- oder Fortran-Programm enthält eine ad-hoc, informell spezifizierte, fehlerbehaftete, langsame Implementierung der Hälfte von Common Lisp." Wenn Sie jemals in Ihrer Anwendung eine völlig neue Vorlagen- / Ablaufsteuerungssprache erfunden haben, dann wissen Sie wahrscheinlich, was ich meine ...
Nebenläufigkeit ist eine einzigartige Stärke von Clojure (auch im Vergleich zu anderen Lisps). Wenn Sie jedoch der Meinung sind, dass die Zukunft der Programmierung das Schreiben von Anwendungen erfordert, die auf hochgradig mehrkernige Maschinen skaliert werden können, sollten Sie sich das wirklich genau ansehen - es ist hübsch wegweisend. Clojure STM (Software Transactional Memory) funktioniert, weil Clojure Unveränderlichkeit und sperrenfreie Nebenläufigkeitskonstruktionen umfasst, die auf einer neuartigen Trennung von Identität und Status basieren (siehe Rich Hickeys hervorragendes Video zu Identität und Status ). Es wäre sehr schmerzhaft, diese Art der Parallelität auf eine Sprach- / Laufzeitumgebung zu übertragen, in der ein erheblicher Teil der APIs auf veränderlichen Objekten arbeitet.
Funktionale Programmierung - Lisps betont die Programmierung mit Funktionen höherer Ordnung. Sie haben Recht, dass es in OO-Objekten mit Closures / Funktionsobjekten usw. emuliert werden kann. Der Unterschied besteht jedoch darin, dass die gesamte Sprach- und Standardbibliothek so konzipiert ist, dass Sie Code auf diese Weise schreiben können. Beispielsweise sind Clojure- Sequenzen faul und können im Gegensatz zu ArrayLists oder HashMaps in C # / Java unendlich lang sein. Dies erleichtert das Ausdrücken einiger Algorithmen erheblich , z. B. implementiert Folgendes eine unendliche Liste von Fibonacci-Zahlen:
(def fibs (lazy-cat '(0 1) (map + fibs (drop 1 fibs))))
Dynamische Typisierung - Lisps tendieren dazu, am "dynamischen" Ende des Spektrums der funktionalen Programmiersprachen zu stehen (im Gegensatz zu Sprachen wie Haskell mit einer sehr starken und schönen Konzeption statischer Typen). Dies hat sowohl Vor- als auch Nachteile, aber ich würde argumentieren, dass dynamische Sprachen aufgrund ihrer größeren Flexibilität die Programmierproduktivität begünstigen. Diese dynamische Eingabe ist mit geringen Kosten für die Laufzeitleistung verbunden. Wenn Sie dies jedoch stören, können Sie Ihrem Code immer Tipptipps hinzufügen, um statische Eingaben zu erhalten. Grundsätzlich erhalten Sie standardmäßig Komfort und Leistung, wenn Sie bereit sind, ein wenig zusätzliche Arbeit zu leisten, um dies zu erreichen.
Interaktive Entwicklung - ich denke, Sie können eine Art REPL für C # 4.0 erhalten, aber in Lisp ist dies eher eine Standardübung als eine Neuheit. Sie müssen überhaupt keinen Kompilierungs-, Erstellungs- oder Testzyklus durchführen. Sie schreiben Ihren Code direkt in die laufende Umgebung und kopieren ihn erst später in Ihre Quelldateien. Es bringt Ihnen eine ganz andere Art des Codierens bei, bei der Sie die Ergebnisse sofort sehen und Ihre Lösung iterativ verfeinern.
Ein paar kurze Kommentare zu den Dingen, die Sie als "vermisst" ansehen:
Ich persönlich finde die Vorteile von Clojure / Lisp ziemlich überzeugend, und ich plane nicht, auf C # / Java zurückzukommen (über das hinaus, was ich zum Ausleihen aller nützlichen Bibliotheken benötige!).
Es hat jedoch ein paar Monate gedauert, bis ich alles richtig verstanden hatte. Wenn Sie wirklich daran interessiert sind, Lisp / Clojure als eine aufschlussreiche Lernerfahrung zu lernen, gibt es keinen Ersatz für das Eintauchen und Zwingen, einige Dinge umzusetzen.
quelle
C # hat viele Funktionen, aber die Mischung fühlt sich anders an. Dass eine Sprache eine Funktion hat, bedeutet nicht, dass sie einfach zu bedienen, paradigmatisch und gut in den Rest der Sprache integriert ist.
Common Lisp hat diesen Mix:
Nun, andere Sprachen, wie C #, haben das auch. Aber oft nicht mit der gleichen Betonung.
C # hat
Es hilft nicht, dass C # zum Beispiel Code-Manipulationsfunktionen hat, wenn sie nicht so verbreitet sind wie in Lisp. In Lisp gibt es nicht viele Programme, die Makros nicht auf einfache und komplexe Weise einsetzen. Die Verwendung von Code-Manipulation ist in Lisp wirklich ein großes Feature. Das Emulieren einiger Verwendungen ist in vielen Sprachen möglich, macht es jedoch nicht zu einer zentralen Funktion wie in Lisp. Beispiele finden Sie in Büchern wie 'On Lisp' oder 'Practical Common Lisp'.
Gleiches gilt für die interaktive Entwicklung. Wenn Sie ein großes CAD-System entwickeln, erfolgt der größte Teil der Entwicklung interaktiv vom Editor und der REPL - direkt in die laufende CAD-Anwendung. Sie können einen Großteil davon in C # oder anderen Sprachen emulieren - häufig durch Implementierung einer Erweiterungssprache. Für Lisp wäre es dumm, die in Entwicklung befindliche CAD-Anwendung neu zu starten, um Änderungen hinzuzufügen. Das CAD-System würde dann auch ein erweitertes Lisp enthalten, das Sprachfunktionen (in Form von Makros und symbolischen Beschreibungen) zur Beschreibung von CAD-Objekten und ihren Beziehungen (Teil von, enthalten, ...) hinzugefügt hätte. In C # kann man ähnliche Dinge tun, aber in Lisp ist dies der Standardentwicklungsstil.
Wenn Sie komplexe Software mithilfe eines interaktiven Entwicklungsprozesses entwickeln oder Ihre Software einen wesentlichen symbolischen Anteil hat (Metaprogrammierung, andere Paradigmen, Computeralgebra, Musikkomposition, Planung, ...), ist Lisp möglicherweise das Richtige für Sie.
Wenn Sie in der Windows-Umgebung arbeiten (ich nicht), hat C # einen Vorteil, da es eine Hauptentwicklungssprache von Microsoft ist. Microsoft unterstützt keine Form Lisp.
Du schreibst:
Common Lisp fügt Klassen keine Namespaces hinzu. Namespaces werden von Symbolpaketen bereitgestellt und sind von Klassen unabhängig. Wenn Sie CLOS verwenden, müssen Sie Ihre Herangehensweise an die objektorientierte Programmierung neu ausrichten.
Verwenden Sie den Lisp-Compiler. Es informiert Sie über unbekannte Variablen, kann Stilempfehlungen enthalten (abhängig vom verwendeten Compiler), gibt Tipps zur Effizienz, ... Der Compiler ist nur einen Tastendruck entfernt.
Die kommerziellen Lisps wie LispWorks und Allegro CL bieten ähnliche Dinge unter Windows.
All das bieten die kommerziellen Lisps wie LispWorks und Allegro CL unter Windows.
quelle
Rainier Joswig sagte es am besten.
Für mich ist die Kraft von Lisp nicht nur anhand einer Liste von Lisp-Funktionen zu verstehen. Für Lisp und insbesondere für Common Lisp ist das Ganze größer als die Summe der Teile. Es ist nicht nur die REPL plus s-Ausdrücke plus Makros plus Multimethoden-Versand plus Abschlüsse plus Pakete plus CLOS plus Neustarts plus X, Y und Z. Es ist das Ganze, was alles genial integriert ist. Es ist die alltägliche Erfahrung, die sich sehr von der alltäglichen Erfahrung anderer Programmiersprachen unterscheidet.
Lisp sieht anders aus, es wird anders verwendet, man geht anders an die Programmierung heran, wenn man es benutzt. Es ist elegant, komplett, groß und nahtlos. Es ist nicht ohne seine eigenen Schönheitsfehler und Peccadillos. Es gibt zweifellos Vergleiche mit anderen Sprachen, bei denen es möglicherweise nicht gelingt, die Nase vorn zu haben. Aber in keiner Weise ist Lisp nur die Sprache X plus REPL und Makros oder die Sprache Y plus Versand und Neustart mit mehreren Methoden.
quelle
In der Regel sollte ein Makro für etwas verwendet werden, das nicht als Funktionsaufruf ausgedrückt werden kann. Ich weiß nicht, ob es in C # eine switch-ähnliche Bedingung gibt (ähnlich wie in C, als switch (form) {case ...}), aber nehmen wir an, dass dies der Fall ist und nahezu dieselben Einschränkungen hat ( erfordert einen integralen Typ).
Nehmen wir nun weiter an, dass Sie etwas mögen, das so aussieht, aber auf Saiten versendet. In lisp (naja, speziell in Common Lisp und wahrscheinlich in anderen) ist das "nur" ein Makro entfernt, und wenn Sie den Benutzer darauf beschränken, konstante (wahrscheinlich nicht mühsame) Zeichenfolgen zum Abgleichen zu haben, können Sie (zur Kompilierungszeit) rechnen Eine minimale Folge von Tests, um festzustellen, welche Groß- / Kleinschreibung zutrifft (oder eine Hash-Tabelle zu verwenden, um möglicherweise Abschlüsse zu speichern).
Während es möglich sein mag, dies als Funktion auszudrücken (Vergleichszeichenfolge, Liste der auszuführenden Fälle und Abschlüsse), würde es wahrscheinlich nicht wie "normaler Code" aussehen, und hier haben Lisp-Makros einen bestimmten Gewinn. Sie haben wahrscheinlich schon einige Makros oder spezielle Formen taht sind Makro-like, wie verwendet
defun
,defmacro
,setf
,cond
,loop
und viele mehr.quelle
Dies ist der beste erklärende Artikel, den ich gefunden habe und der den Leser von der Oberfläche der Befürwortung von Lisp nimmt, um einige der tieferen Implikationen der verwendeten Konzepte zu zeigen:
http://www.defmacro.org/ramblings/lisp.html
Ich erinnere mich, dass ich eine Idee wie diese an anderer Stelle gelesen habe: Andere Sprachen haben verschiedene Merkmale von Lisp übernommen; Speicherbereinigung, dynamische Eingabe, anonyme Funktionen, Schließungen für ein besseres C ++ / C / Algol. Um jedoch die volle Leistungsfähigkeit von Lisp nutzen zu können, benötigen Sie alle wesentlichen Funktionen. Zu diesem Zeitpunkt verfügen Sie über einen weiteren Lisp-Dialekt, eine eigenständige Sprachfamilie, die es in der einen oder anderen Form seit über 50 Jahren gibt.
quelle