Haskell, Lisp und Ausführlichkeit [geschlossen]

104

Ich bin gespannt, wie "angenehm" (um einen schrecklichen Begriff zu verwenden) es ist, Code in Haskell vs. Lisp zu schreiben.

Einige Hintergrundinformationen: Ich lerne jetzt Haskell, nachdem ich zuvor mit Scheme und CL gearbeitet habe (und einen kleinen Ausflug in Clojure). Traditionell könnte man mich wegen der Prägnanz und Schnelligkeit, die sie bieten, als Fan dynamischer Sprachen betrachten. Ich habe mich schnell in Lisp-Makros verliebt, da es mir eine weitere Möglichkeit gab, Ausführlichkeit und Boilerplate zu vermeiden.

Ich finde Haskell unglaublich interessant, da es mich in Codierungsmethoden einführt, von denen ich nicht wusste, dass sie existieren. Es hat definitiv einige Aspekte, die dazu beitragen, Agilität zu erreichen, wie das einfache Schreiben von Teilfunktionen. Ich bin jedoch ein bisschen besorgt über den Verlust von Lisp-Makros (ich nehme an, ich verliere sie; um ehrlich zu sein, ich habe vielleicht noch nichts davon erfahren?) Und das statische Typisierungssystem.

Würde es jemandem, der in beiden Welten eine anständige Menge an Codierung durchgeführt hat, etwas ausmachen, zu kommentieren, wie sich die Erfahrungen unterscheiden, was Sie bevorzugen und ob diese Präferenz situativ ist?

J Cooper
quelle

Antworten:

69

Kurze Antwort:

  • Fast alles, was Sie mit Makros tun können, können Sie mit einer Funktion höherer Ordnung tun (und ich schließe Monaden, Pfeile usw. ein), aber es erfordert möglicherweise mehr Nachdenken (aber nur beim ersten Mal, und es macht Spaß und Sie werden ein besserer Programmierer dafür) und
  • Das statische System ist so allgemein gehalten, dass es Ihnen nie im Weg steht, und es ist überraschenderweise tatsächlich "hilfreich, um Agilität zu erreichen" (wie Sie sagten), denn wenn Ihr Programm kompiliert wird, können Sie fast sicher sein, dass es korrekt ist, sodass Sie es mit dieser Gewissheit versuchen können Dinge heraus, vor denen Sie sonst Angst haben könnten - es gibt ein "dynamisches" Gefühl beim Programmieren, obwohl es nicht dasselbe ist wie bei Lisp.

[Hinweis: Es gibt eine " Vorlagen-Haskell ", mit der Sie Makros wie in Lisp schreiben können, aber genau genommen sollten Sie sie niemals benötigen .]

ShreevatsaR
quelle
15
Aus Conor McBride, zitiert von Don Stewart: "Ich stelle mir Typen gerne als Verzerrung unserer Schwerkraft vor, damit die Richtung, in die wir reisen müssen [um korrekte Programme zu schreiben]," bergab "wird." Das Typsystem macht es überraschend einfach, korrekte Programme zu schreiben. Siehe diesen Beitrag und seine erneuten Freigaben.
ShreevatsaR
3
Funktionen höherer Ordnung können Makros nicht ersetzen, und tatsächlich hat CL aus irgendeinem Grund beides. Die wahre Stärke von Makros in CL besteht darin, dass der Entwickler neue Sprachfunktionen einführen kann, mit denen die Lösung eines Problems besser ausgedrückt werden kann, ohne auf eine neue Version der Sprache wie in Haskell oder Java warten zu müssen. Wenn Haskell diese Fähigkeit hätte, müssten Haskell-Autoren beispielsweise keine GHC-Erweiterungen schreiben, da sie von den Entwicklern jederzeit selbst als Makros implementiert werden könnten.
mljrg
@mljrg Hast du ein konkretes Beispiel? Siehe die Kommentare zu Hibou57s Antwort unten, wo sich ein angebliches Beispiel als zweifelhaft herausstellte. Es würde mich interessieren, was Sie meinen (z. B. Haskell-Code mit und ohne Makros).
ShreevatsaR
4
Nehmen Sie Curry aus Haskell heraus. Könnten Sie es mit dem implementieren, was in Haskell übrig bleiben würde? Ein weiteres Beispiel: Angenommen, Haskell unterstützt keinen Mustervergleich. Können Sie ihn selbst hinzufügen, ohne dass die Entwickler von GHC ihn unterstützen müssen? In CL können Sie Makros verwenden, um die Sprache nach Belieben zu erweitern. Ich nehme an, das ist der Grund, warum CL die Sprache seit ihrem Standard in den 90er Jahren nicht geändert hat, während Haskell einen nie endenden Fluss von Erweiterungen in GHC zu haben scheint.
mljrg
64

Machen Sie sich zunächst keine Sorgen, dass Sie bestimmte Funktionen wie die dynamische Eingabe verlieren. Da Sie mit Common Lisp, einer bemerkenswert gut gestalteten Sprache, vertraut sind, ist Ihnen vermutlich bewusst, dass eine Sprache nicht auf ihre Funktionen reduziert werden kann. Es geht um ein zusammenhängendes Ganzes, nicht wahr?

In dieser Hinsicht leuchtet Haskell genauso hell wie Common Lisp. Die Funktionen bieten Ihnen eine Programmiermöglichkeit, die den Code extrem kurz und elegant macht. Das Fehlen von Makros wird durch ausgefeiltere (aber ebenfalls schwerer zu verstehende und zu verwendende) Konzepte wie Monaden und Pfeile etwas gemildert. Das statische Typsystem erhöht Ihre Leistung, anstatt sich wie in den meisten objektorientierten Sprachen in den Weg zu stellen.

Andererseits ist das Programmieren in Haskell viel weniger interaktiv als in Lisp, und die enorme Menge an Reflexion in Sprachen wie Lisp passt einfach nicht zu der statischen Sicht der Welt, die Haskell voraussetzt. Die Ihnen zur Verfügung stehenden Werkzeugsätze unterscheiden sich daher erheblich zwischen den beiden Sprachen, sind jedoch schwer miteinander zu vergleichen.

Ich persönlich bevorzuge die Lisp-Programmierweise im Allgemeinen, da sie meiner Meinung nach besser zu meiner Arbeitsweise passt. Dies bedeutet jedoch nicht, dass Sie dazu verpflichtet sind.

Matthias Benkard
quelle
4
Könnten Sie etwas näher auf "Programmieren in Haskell ist viel weniger interaktiv" eingehen? Bietet GHCi nicht wirklich alles, was Sie brauchen?
Johannes Gerer
3
@JohannesGerer: Ich habe es nicht ausprobiert, aber soweit ich gelesen habe, ist GHCi keine Shell im laufenden Image, in der Sie beliebige Teile des gesamten Programms neu definieren und erweitern können, während es ausgeführt wird. Außerdem erschwert die Haskell-Syntax das programmgesteuerte Kopieren von Programmfragmenten zwischen Repl und Editor.
Svante
13

In Haskell ist weniger Metaprogrammierung erforderlich als in Common Lisp, da vieles um Monaden herum strukturiert werden kann und die hinzugefügte Syntax eingebettete DSLs weniger baumartig aussehen lässt, aber es gibt immer Template Haskell, wie von ShreevatsaR erwähnt , und sogar Liskell (Haskell-Semantik + Lisp) Syntax), wenn Sie die Klammern mögen.

Herrmann
quelle
1
Der Liskell- Link ist tot, aber heutzutage gibt es Hackett .
Will Ness
10

In Bezug auf Makros ist hier eine Seite, die darüber spricht: Hallo Haskell, Goodbye Lisp . Es erklärt eine Sichtweise, in der Makros in Haskell einfach nicht benötigt werden. Es kommt mit einem kurzen Beispiel zum Vergleich.

Beispielfall, in dem ein LISP-Makro erforderlich ist, um die Bewertung beider Argumente zu vermeiden:

(defmacro doif (x y) `(if ,x ,y))

Beispielfall, in dem Haskell beide Argumente nicht systematisch bewertet, ohne dass eine Makrodefinition erforderlich ist:

doif x y = if x then (Just y) else Nothing

Und voilà

Hibou57
quelle
17
Das ist ein weit verbreitetes Missverständnis. Ja, in Haskell bedeutet Faulheit, dass Sie keine Makros benötigen, wenn Sie vermeiden möchten, einige Teile eines Ausdrucks auszuwerten, aber dies ist nur die trivialste Teilmenge aller Makroverwendungen. Google für "The Swine Before Perl" für einen Vortrag, der ein Makro demonstriert, das mit Faulheit nicht möglich ist. Auch wenn Sie noch einige Bit wollen streng sein, dann kann man nicht tun, als eine Funktion - Spiegelung der Tatsache , dass Scheme delaykeine Funktion sein kann.
Eli Barzilay
8
@Eli Barzilay: Ich finde dieses Beispiel nicht sehr überzeugend. Hier ist eine vollständige, einfache Haskell-Übersetzung von Folie 40: pastebin.com/8rFYwTrE
Reid Barton
5
@ Eli Barzilay: Ich verstehe deine Antwort überhaupt nicht. accept ist das (E) DSL. Die acceptFunktion ist das Analogon des auf den vorherigen Seiten beschriebenen Makros, und die Definition von vist genau parallel zur Definition von vin Schema auf Folie 40. Die Funktionen Haskell und Schema berechnen dasselbe mit derselben Bewertungsstrategie. Im besten Fall können Sie mit dem Makro dem Optimierer mehr von der Struktur Ihres Programms zur Verfügung stellen. Sie können dies kaum als Beispiel bezeichnen, bei dem Makros die Ausdruckskraft der Sprache auf eine Weise erhöhen, die nicht durch eine verzögerte Bewertung repliziert wird.
Reid Barton
5
@Eli Barzilay: In einem hypothetischen faulen Schema könnten Sie dies schreiben: pastebin.com/TN3F8VVE Meine allgemeine Behauptung ist, dass dieses Makro Ihnen sehr wenig einbringt: etwas andere Syntax und eine einfachere Zeit für den Optimierer (aber es wäre nicht wichtig ein "ausreichend intelligenter Compiler"). Im Gegenzug haben Sie sich in einer ausdruckslosen Sprache gefangen; Wie definieren Sie einen Automaten, der mit einem Buchstaben übereinstimmt, ohne alle aufzulisten? Ich weiß auch nicht, was Sie unter "Verwendung in allen Unterlisten" oder "erforderliche Verwendung von where mit eigenem Geltungsbereich" verstehen.
Reid Barton
15
OK ich gebe auf. Anscheinend ist Ihre Definition von DSL "das Argument für ein Makro", und so ist mein Beispiel für ein faules Schema kein DSL, obwohl es syntaktisch isomorph zum Original ist ( automatonWerden letrec, :Werden accept, ->Nichts in dieser Version werden). Was auch immer.
Reid Barton
9

Ich bin ein Common Lisp-Programmierer.

Nachdem ich vor einiger Zeit Haskell ausprobiert hatte, war es mein persönliches Fazit, bei CL zu bleiben.

Gründe dafür:

Haskell hat natürlich seine eigenen Vorzüge und macht einige Dinge auf eine grundlegend andere Art und Weise, aber es schneidet es auf lange Sicht für mich einfach nicht ab.

Leslie P. Polzer
quelle
Hey, hast du zufällig den Titel des Costanza-Papiers, mit dem du verlinkt hast? Sieht aus wie diese Datei verschoben wurde.
michiakig
2
Beachten Sie, dass auch haskell die Präfixsyntax unterstützt, aber ich würde sagen, dass monad >> = mit dieser sehr, sehr hässlich wäre. Ich bin auch nicht damit einverstanden, dass Unreinheit ein Segen ist: P
Alternative
2
Ich mag diese Randnotiz: Wir haben noch keine empirischen Daten gesammelt, ob dieses Problem ernsthafte Probleme in realen Programmen verursacht.
Jerome Baum
22
Keines der Beispiele in diesem Artikel (Pascal Costanza, Dynamische vs. statische Typisierung - Eine musterbasierte Analyse ) gilt für Haskell. Sie sind alle Java-spezifisch (oder genauer gesagt spezifisch für "objektorientierte Programmierung" ), und ich kann keines dieser Probleme in Haskell feststellen. In ähnlicher Weise sind alle Ihre anderen Argumente umstritten: Man kann auch sagen, dass Haskell "rein und daher besser für das schnelle Prototyping geeignet" ist, dass die Präfixsyntax nicht obligatorisch ist, dass es keine große Auswahl an Compilern gibt, die verschiedene Dinge tun usw.
ShreevatsaR
11
Dieses Papier ist für Haskell in der Tat fast völlig irrelevant. " dilbert = dogbert.hire(dilbert);" ?? Ich bezweifle, dass viele Haskell-Programmierer dies sogar lesen können, ohne ein wenig zu zucken.
links um den
6

In Haskell können Sie eine if-Funktion definieren, die in LISP nicht möglich ist. Dies ist aufgrund der Faulheit möglich, die eine größere Modularität der Programme ermöglicht. Dieses klassische Papier: Warum FP wichtig ist von John Hughes, erklärt, wie Faulheit die Kompositionsfähigkeit verbessert.

luntain
quelle
5
Das Schema (einer der beiden wichtigsten LISP-Dialekte) hat tatsächlich eine verzögerte Bewertung, obwohl es nicht wie in Haskell standardmäßig ist.
Randy Voet
7
(defmacro doif (xy) `(if, x, y))
Joshua Cheek
4
Ein Makro ist nicht das gleiche wie eine Funktion - Makros mit Funktionen höherer Ordnung nicht gut funktionieren wie foldzum Beispiel während nicht-strikte Funktionen tun .
Tikhon Jelvis
5

Es gibt wirklich coole Dinge, die Sie in Lisp mit Makros erreichen können, die in Haskell (wenn möglich) umständlich sind. Nehmen Sie zum Beispiel das "Memoize" -Makro (siehe Kapitel 9 von Peter Norvigs PAIP). Damit können Sie eine Funktion definieren, z. B. foo, und dann einfach auswerten ('foo auswendig lernen'), wodurch die globale Definition von foo durch eine gespeicherte Version ersetzt wird. Können Sie den gleichen Effekt in Haskell mit Funktionen höherer Ordnung erzielen?


quelle
3
Nicht ganz (AFAIK), aber Sie können etwas Ähnliches tun, indem Sie die Funktion so ändern
j_random_hacker
6
Sie können einer faulen Datenstruktur foo hinzufügen, in der der Wert nach der Berechnung gespeichert wird. Dies wird effektiv das gleiche sein.
Theo Belaire
Alles in Haskell wird gespeichert und wahrscheinlich eingefügt, wenn dies vom Haskell-Compiler standardmäßig benötigt wird.
aoeu256
4

Während ich meine Haskell-Lernreise fortsetze, scheint es eine Sache zu sein, die hilft, Makros zu "ersetzen", die Fähigkeit, eigene Infix-Operatoren zu definieren und deren Priorität und Assoziativität anzupassen. Etwas kompliziert, aber ein interessantes System!

J Cooper
quelle