C # Dev - Ich habe Lisps ausprobiert, aber ich verstehe es nicht


37

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?


4
Da Sie bereits in einem funktionalen Stil programmieren und sich anscheinend ziemlich stark auf die Windows-Infrastruktur verlassen, sehe ich keinen zwingenden Grund für Sie, von C # zu wechseln. Fast jeder, den ich kenne, verwendet Mac oder Linux, daher hängt vieles davon ab, mit wem Sie arbeiten (ich habe seit 3.1 alle Windows-Versionen verwendet, arbeite aber generell immer noch lieber mit plattformübergreifender Software und * nix-Systemen). aber dann mache ich keine native GUI-Arbeit, alle Server und Web-UI-Zeug)
Sean Corfield

2
Sie konnten einen Blick auf haben das Schwein Vor Perl . Das Hören macht Spaß und zeigt anhand eines einfachen Beispiels, wozu Makros gut sind.
Matthias Benkard

4
"Ich habe kein einziges Beispiel gesehen, in dem die Idee eines Makros nicht als Funktion implementiert werden konnte" - ich möchte sehen, wie Sie schreiben ANDoder ORals Funktion. Es könnte getan werden (vorausgesetzt LAMBDA, das ist auch ein Makro), aber ich sehe keinen offensichtlichen Weg, um es zu tun, der nicht völlig zum Kotzen wäre.

1
"Namespaces. ... Clojure scheint das zu haben, CL scheint das nicht zu haben" - können Sie genauer sagen, wonach Sie suchen? Ich habe Clojure nicht wirklich verwendet, aber seine Namespaces ähneln den CL-Paketen.

4
Jonathan: CL verwendet :(oder ::für nicht exportierte Symbole) zum Benennen von Symbolen in Paketen, z. B. würde Ihr Beispiel hier http:sessionund verwenden chat:session.

Antworten:


23

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.


3
Was mich an diesem Argument interessiert, ist, dass ich noch nie die Gelegenheit bekommen habe, eine DSL / DSIL zu implementieren (oder es in meiner Unkenntnis völlig übersehen habe). Könnten Sie die Leistungsfähigkeit von DSLs erläutern? (Sowohl für Entwickler als auch für Benutzer.) Das hört sich so an, als ob es das große Ding ist, das ich wirklich vermisse.

15
@Jonathan Mitchem, Sie verwenden bereits viele externe DSLs - Konfigurationsdateien, Build-Skripte, ORM-Konfigurationen, visuelle Codegeneratoren (z. B. Winforms-Editor), XAML, Parser-Generatoren usw. Eingebettete DSLs sind sogar noch besser, als Sie es tun Sie benötigen kein separates Build-Tool, und sie werden in einer einzigen Sprache bereitgestellt. Und Sie müssen mit mindestens zwei der eingebetteten DSLs vertraut sein - regulären Ausdrücken und SQL.
SK-logic

Wow OK. Ich habe sie einfach nie als DSLs angesehen. Jetzt habe ich mich absichtlich von vielem abgewandt, auf der Grundlage von "alles, was ich mit c # machen kann, werde ich mit c # für bleiben". Ich mag es, mit einem Werkzeug zu bleiben, das sich persönlich konsistent verhält. Aber ich verstehe den Wert von DSLs in diesen Beispielen.

2
@ Jonathan Mitchem, das Problem mit einem bestimmten Werkzeug ist, dass es nicht unbedingt für einen bestimmten Zweck geeignet ist. Sie müssen verschiedene Werkzeuge für verschiedene Aufgaben auswählen. Und die Stärke jeder Metasprache, einschließlich Lisp, besteht darin, dass Sie nicht vollständig zu einem anderen Tool wechseln müssen, da Sie Ihre eigenen domänenspezifischen Tools zusätzlich zu Ihrer Hauptsprache erweitern können. Ich schlage vor, Sie werfen einen Blick auf Nemerle, es wäre für einen C # -Entwickler einfacher, es zu verstehen, und seine Makros sind fast so mächtig wie die von Lisp.
SK-logic

3
"Ich mag es, bei einem Tool zu bleiben ..." Richtig gestaltete DSLs in Lisp verbergen nie die volle Leistung von Lisp, sodass Sie immer noch ein Tool haben. Sie ergänzen das Vokabular lediglich auf eine Weise, die die Codierung für eine bestimmte Domäne "natürlicher" macht, dh mit besseren Abstraktionen und einer besseren Gesamtorganisation.

15

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.

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

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.


3
Ohne irgendeinen Code zu berühren, durchlief ich das Szenario der Implementierung eines "Wenn" ohne Makros in meinem Kopf, sowohl in Clojure als auch in C #. Es kann effektiv (oder wörtlich) nicht getan werden. Ich gebe zu, das ist ordentlich. Aber mir fehlt der Zusammenhang, wie nützlich das für mich ist. Was kann ich mit Makros machen (außer "wenn" schreiben), die ich mit cleverem funktionalem oder OO-Design nicht machen kann? "Wie könnte mir das helfen?" Ist kein gültiges Kriterium für die Bewertung einer Sprache, aber es ist für die Bewertung, ob ich sie verwenden werde. (Ich wirklich will ein Lisp wert Schalt zu sein, aber es hat nicht getroffen den Punkt des eine brauchbare Alternative zu werden.)

1
Es wird ein bisschen dauern, bis ich dieses Beispiel herausgefunden habe. Für mich wird beim "Speichern von Boilerplate-Code" normalerweise der Begriff "Refactoring" verwendet, weshalb mich die Erklärungen nicht überzeugt haben. Ich habe eine Lösung, die in jedem Fall funktioniert, in dem ich sie angewendet habe. Ich denke dann ist die Frage, wie Sie Fälle identifizieren, in denen Sie ein Makro verwenden können?

1
Ich bin damit einverstanden, dass sich Makros lohnen, aber es ist nicht wahr, dass ein my-if sie erfordert, es kann als Funktion höherer Ordnung geschrieben werden (siehe ein CL-Beispiel unten). Sie brauchen Makros nur dann wirklich, wenn Sie den Code durchgehen oder in einen neuen lexikalischen Kontext stellen möchten. (defun my-if (test then &optional else) (cond (test (funcall then)) ('otherwise (funcall else))))

1
Grundsätzlich haben Sie bei einer Funktion die Wahl, entweder zitierten Code zu akzeptieren, den Sie verwenden können, aber es fehlt ihr lexikalischer Kontext, da Sie ihn im lexikalischen Kontext Ihrer Funktion ausführen. Oder Sie akzeptieren Closures, die ihren lexikalischen Kontext beibehalten, aber Sie können sie nur anrufen, nicht durchgehen oder etwas zu ihrem lexikalischen Kontext hinzufügen. Um beides zu tun, benötigen Sie ein Makro, das den Code lesen und umbrechen kann, bevor seine Erweiterung in den lexikalischen Kontext des Makroaufrufs gestellt wird.

7
@Jonathan Mitchem, die LINQ-Syntax ist ein schönes Beispiel dafür, was mit Makros gemacht werden kann, musste aber stattdessen in einem Sprachkern implementiert werden, da die Sprache keine anständige Metaprogrammierung unterstützt.
SK-logic

14

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.

  • Einfache Syntax => Macht - Lisps handelt von der einfachsten Syntax, die funktionieren könnte. S-Ausdrücke sind alles, was Sie brauchen. Wenn Sie einmal "(Funktionsaufruf arg1 arg2 arg3)" angeklickt haben, haben Sie es im Grunde genommen verstanden. Der Rest ist nur die Auswahl der richtigen Funktionsaufrufe und Argumente. Scheint fast trivial einfach, aber die Tatsache ist, dass diese Einfachheit Lisps so viel Kraft und Flexibilität verleiht und insbesondere die Meta-Programmierfähigkeiten ermöglicht, für die Lisp berühmt ist. zB wenn ich will ein Makro mehrere verschiedene Funktionen auf die gleichen Argumente nennen ich nur so etwas wie die folgenden (nicht , dass dies nicht nur Runtime - Funktion Anwendung - es ist ein Makro , das Code kompiliert erzeugt , als ob Sie alle , die einzelnen Funktionsaufrufe geschrieben hatte von Hand):
(defmacro print-many [functions args]  
  `(do   
     ~@(map   
        (fn [function] `(println (~function ~@args)))   
        functions)))

(print-many [+ - * /] [10 7 2])  
19  
1  
140  
5/7
  • 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:

  • Wie Sie sagen, hat Clojure Namespaces. Tatsächlich handelt es sich um dynamische Namespaces, die Sie zur Laufzeit aktualisieren können. Dies bietet einige überraschende Vorteile für die interaktive Entwicklung. Ich hatte eine Menge Spaß daran, Funktionen unter der Nase eines laufenden Threads neu zu definieren oder eine Live-Datenstruktur zu hacken ...
  • Kompilierungs- / Entwurfszeitunterstützung - nicht wirklich relevant, wenn Sie bei einer REPL codieren - Sie tun es einfach und sehen das Ergebnis direkt. Allerdings wird Clojure zunehmend besser IDE-unterstützt, z. B. das CounterClockwise-Plugin für Eclipse .
  • GUI-Entwicklung - Ja, Sie müssen eine neue API lernen, aber das wird immer der Fall sein, wenn Sie die Sprache wechseln. Spezifische Wrapper / Beispiele, die recht einfach zu verwenden sind. In dieser Frage zur GUI-Entwicklung von Clojure finden Sie einige schöne Beispiele
  • GUI-Debugging: Dies funktioniert mit dem GUI-Debugger in Eclipse, vorausgesetzt, Sie verwenden CounterClockwise oder Enclojure, wenn Sie Netbeans verwenden. Trotzdem nutze ich diese Funktion nicht - ich finde das interaktive Debuggen auf der REPL produktiver.

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.


1
Vielen Dank für die tolle Aufzählung der Ideen. Über das IEnumerable <> -Konstrukt in C # verwende ich eigentlich häufig Lazy Sequences (und habe seit 2005 oder so keine ArrayList mehr berührt), aber ich verstehe die Idee. Die Nebenläufigkeitsprimitive von Clojure sind äußerst beeindruckend. Ich habe vor ein paar Jahren Grid-Computing in C # durchgeführt - parallel auf vielen Multi-Core-Computern - und ich glaube immer noch, dass dies die Zukunft ist. Wahrscheinlich ist die größte Idee, die ich von allen bekommen habe, "man muss sich wirklich nur vertiefen und es erleben, es kann nicht wirklich erklärt werden". Also werde ich weiter graben und weiter erleben.

1
Cool, froh geholfen zu haben - und das ist absolut der richtige Geist !!
Mikera

10

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:

  • S-Ausdrücke als Datensyntax
  • Code als Daten führt zu Makros und anderen Techniken der symbolischen Berechnung
  • dynamische Sprache ermöglicht Laufzeitänderungen (späte Bindung, MOP, ...)
  • stark getippt, dynamisch getippt
  • Interaktive Verwendung mit inkrementellem Compiler oder Interpreter
  • Evaluator als Core Execution Engine
  • verwalteter Speicher mit Garbage Collection
  • Hybride Sprache mit starkem Einsatz von imperativer, funktionaler und objektorientierter Programmierung. Andere Paradigmen können hinzugefügt werden (Regeln, Logik, Einschränkungen, ...).

Nun, andere Sprachen, wie C #, haben das auch. Aber oft nicht mit der gleichen Betonung.

C # hat

  • C-ähnliche Syntax
  • Meist imperativ objektorientiert, mit einigen FP-Merkmalen
  • statisch getippt
  • Meist Batch-Verwendung mit einem Batch-Compiler
  • verwalteter Speicher mit Garbage Collection

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:

  • 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).

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.

  • Hervorragende Unterstützung für Kompilierung und Entwurfszeit Das Typensystem ermöglicht es mir, die "Korrektheit" der Datenstrukturen zu bestimmen, die ich übergebe, wenn die Rechtschreibfehler in Echtzeit unterstrichen sind. Ich muss nicht bis zur Laufzeit warten, um zu wissen, dass Code-Verbesserungen (z. B. die Verwendung eines FP-Ansatzes anstelle eines Imperativ-Ansatzes) automatisch empfohlen werden

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.

  • GUI-Entwicklungstools: WinForms und WPF (Ich weiß, dass Clojure Zugriff auf die Java-GUI-Bibliotheken hat, aber sie sind mir völlig fremd.)

Die kommerziellen Lisps wie LispWorks und Allegro CL bieten ähnliche Dinge unter Windows.

  • GUI-Debugging-Tools: Haltepunkte, Step-In, Step-Over, Wertprüfer (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 teilweise an dem VS GUI-gesteuerten Ansatz interessiert)

All das bieten die kommerziellen Lisps wie LispWorks und Allegro CL unter Windows.


1
Meine Kritik bezieht sich nicht wirklich auf Lisps. Ich finde sie ziemlich fähig. Ich bin auf der Suche nach dem, was ein Lisp tun / geben kann, was ich noch nicht getan habe. Ich verstehe voll und ganz, dass die meisten C # -Entwickler nicht viele der "neuen" Sprachfunktionen verwenden. Ich denke, der größere Punkt ist, dass ich, und ehrlich gesagt, außerhalb der GUI, fast in einem 100% FP-Stil schreibe. FP war ein konzeptioneller Sprung, aber es hat sich gelohnt. Ein Wechsel zu einem Lisp, von dem aus ich bin, scheint jedoch wenig zu nützen. Ich hoffe, dass es einen Grund gibt, warum ich es weiter versuchen sollte.

@ Jonathan Mitchem: C # sieht nicht wirklich aus wie eine FP-Sprache, auch wenn die Sprache einige Funktionen hat, die es unterstützen, und einige Bibliotheken könnten diese verwenden. Wenn Sie eine FP-Programmierung im Microsoft-Ökosystem durchführen möchten, ist F # möglicherweise das Richtige für Sie.

@Rainer Nein, es sieht nicht aus wie eine FP-Sprache, aber es kann es, und das ziemlich kompakt. Und wenn ich imperativen Code oder OO will / brauche, kann ich das auch. (Etwas abseits des Themas: Ich halte F # nicht wirklich für eine ernsthafte Sprache, in die man sich vertiefen sollte. Ich würde mich lieber mit Monaden in Haskell beschäftigen. Aber als ich ein C ++ - Entwickler war und C # herauskam, dachte ich nicht darüber nach Auch eine "echte" Sprache (und das war es damals nicht, imho])


@ Jonathan Mitchem: warum soll ich schauen? Ich schrieb, dass C # einige FP-Funktionen unterstützt. Es wurde nur zu einer imperativen objektorientierten Sprache hinzugefügt und macht sie nicht idiomatisch, wie zum Beispiel in Lisp, Scheme und insbesondere nicht im Vergleich zu SML, F #, Haskell oder anderen hauptsächlich funktionalen Sprachen. Mein Punkt ist: Einige FP ist in C # möglich, aber es ist kein Kernmerkmal.

5

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.


3
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

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, loopund viele mehr.


2

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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.