(Ich hoffe, diese Frage ist themenbezogen. Ich habe versucht, nach einer Antwort zu suchen, aber keine endgültige Antwort gefunden. Wenn diese nicht zum Thema gehört oder bereits beantwortet wurde, moderieren / entfernen Sie sie bitte.)
Ich erinnere mich, dass ich den halb scherzhaften Kommentar über Haskell als beste imperative Sprache ein paar Mal gehört / gelesen habe , was natürlich seltsam klingt, da Haskell normalerweise am besten für seine funktionalen Merkmale bekannt ist.
Meine Frage ist also, welche Eigenschaften / Merkmale (wenn überhaupt) von Haskell Anlass geben, zu rechtfertigen, dass Haskell als die beste imperative Sprache angesehen wird - oder ist es eigentlich eher ein Witz?
Antworten:
Ich halte es für eine halbe Wahrheit. Haskell hat eine erstaunliche Fähigkeit zu abstrahieren, und dazu gehört auch die Abstraktion über imperative Ideen. Zum Beispiel hat Haskell keine eingebaute imperative while-Schleife, aber wir können sie einfach schreiben und jetzt tut es:
Diese Abstraktionsebene ist für viele imperative Sprachen schwierig. Dies kann in imperativen Sprachen erfolgen, die Verschlüsse haben; z.B. Python und C #.
Haskell hat aber auch die (höchst einzigartige) Fähigkeit, zulässige Nebenwirkungen zu charakterisieren mithilfe der Monad-Klassen . Zum Beispiel, wenn wir eine Funktion haben:
Dies kann eine "zwingende" Funktion sein, aber wir wissen, dass sie nur zwei Dinge tun kann:
Es kann nicht auf der Konsole gedruckt oder Netzwerkverbindungen usw. hergestellt werden. In Kombination mit der Abstraktionsfunktion können Sie Funktionen schreiben, die auf "jede Berechnung, die einen Stream erzeugt" usw. wirken.
Es geht wirklich nur um Haskells Abstraktionsfähigkeiten, die es zu einer sehr guten imperativen Sprache machen.
Die falsche Hälfte ist jedoch die Syntax. Ich finde Haskell ziemlich ausführlich und umständlich in einem imperativen Stil zu verwenden. Hier ist ein Beispiel für eine Berechnung im imperativen Stil unter Verwendung der obigen
while
Schleife, die das letzte Element einer verknüpften Liste findet:All dieser IORef-Müll, das doppelte Lesen, das Ergebnis eines Lesevorgangs binden zu müssen, fmapping (
<$>
), um das Ergebnis einer Inline-Berechnung zu verarbeiten ... alles sieht nur sehr kompliziert aus. Es macht sehr viel Sinn von einem funktionalen Sicht ist dies , aber zwingende Sprachen neigen dazu, die meisten dieser Details unter den Teppich zu kehren, um ihre Verwendung zu vereinfachen.Zugegeben, vielleicht, wenn wir einen anderen verwendet haben
while
wäre es sauberer Kombinator Stils verwenden würden. Wenn Sie diese Philosophie jedoch weit genug bringen (indem Sie eine Vielzahl von Kombinatoren verwenden, um sich klar auszudrücken), gelangen Sie wieder zur funktionalen Programmierung. Haskell im imperativen Stil "fließt" einfach nicht wie eine gut gestaltete imperative Sprache, z. B. Python.Zusammenfassend könnte Haskell mit einem syntaktischen Facelifting die beste imperative Sprache sein. Aber aufgrund der Natur von Facelifts würde es etwas innerlich Schönes und Reales durch etwas äußerlich Schönes und Falsches ersetzen.
EDIT : Kontrast
lastElt
zu dieser Python-Transliteration:Gleiche Anzahl von Zeilen, aber jede Zeile hat einiges weniger Rauschen.
BEARBEITEN 2
So sieht ein reiner Ersatz in Haskell aus:
Das ist es. Oder wenn Sie mir verbieten, Folgendes zu verwenden
Prelude.last
:Oder, wenn Sie es an die Arbeit auf jedem wollen
Foldable
Datenstruktur und erkennen , dass Sie nicht wirklich brauchenIO
, um Griff - Fehler:mit
Map
zum Beispiel:Der
(.)
Operator ist die Funktionszusammensetzung .quelle
IORef
s implizit zu machen , oder versucht zumindest, Änderungen an GHC zu verhindern. : [Es ist kein Witz, und ich glaube es. Ich werde versuchen, dies für diejenigen zugänglich zu machen, die kein Haskell kennen. Haskell verwendet (unter anderem) die Do-Notation, damit Sie imperativen Code schreiben können (ja, es werden Monaden verwendet, aber machen Sie sich darüber keine Sorgen). Hier sind einige der Vorteile, die Haskell Ihnen bietet:
Einfache Erstellung von Unterprogrammen. Angenommen, ich möchte, dass eine Funktion einen Wert an stdout und stderr druckt. Ich kann Folgendes schreiben und das Unterprogramm mit einer kurzen Zeile definieren:
Einfach Code weiterzugeben. Wenn ich nun die
printBoth
Funktion zum Ausdrucken einer ganzen Liste von Zeichenfolgen verwenden möchte, kann ich dies einfach tun, indem ich meine Unterroutine an diemapM_
Funktion übergebe:Ein anderes Beispiel, obwohl nicht zwingend erforderlich, ist das Sortieren. Angenommen, Sie möchten Zeichenfolgen ausschließlich nach Länge sortieren. Du kannst schreiben:
Welches gibt Ihnen ["b", "cc", "aaaa"]. (Sie können es auch kürzer schreiben, aber im Moment ist das egal.)
Einfach zu verwendender Code. Diese
mapM_
Funktion wird häufig verwendet und ersetzt for-each-Schleifen in anderen Sprachen. Es gibt auch Funktionen,forever
die sich wie eine Weile (true) verhalten, und verschiedene andere Funktionen, denen Code übergeben und auf unterschiedliche Weise ausgeführt werden kann. Daher werden Schleifen in anderen Sprachen in Haskell durch diese Steuerfunktionen ersetzt (die nicht besonders sind - Sie können sie sehr einfach selbst definieren). Im Allgemeinen macht es dies schwierig, die Schleifenbedingung falsch zu machen, genau wie es für jede Schleife schwieriger ist, falsch zu sein als die Langhand-Iteratoräquivalente (z. B. in Java) oder Array-Indexierungsschleifen (z. B. in C).Enthaltene Nebenwirkungen. Nehmen wir an, ich möchte eine Zeile aus stdin lesen und auf stdout schreiben, nachdem ich eine Funktion darauf angewendet habe (wir nennen sie foo). Du kannst schreiben:
Ich weiß sofort, dass
foo
dies keine unerwarteten Nebenwirkungen hat (wie das Aktualisieren einer globalen Variablen oder das Freigeben von Speicher oder was auch immer), da der Typ String -> String sein muss, was bedeutet, dass es sich um eine reine Funktion handelt. Unabhängig davon, welchen Wert ich übergebe, muss jedes Mal das gleiche Ergebnis ohne Nebenwirkungen zurückgegeben werden. Haskell trennt den Nebeneffektcode gut vom reinen Code. In so etwas wie C oder sogar Java ist dies nicht offensichtlich (ändert diese getFoo () -Methode den Status? Sie würden nicht hoffen, aber es könnte tun ...).Es gibt wahrscheinlich noch ein paar weitere Vorteile, aber diese kommen mir in den Sinn.
quelle
Zusätzlich zu dem, was andere bereits erwähnt haben, ist es manchmal nützlich, Nebenwirkungen besonders erstklassig zu haben. Hier ist ein dummes Beispiel, um die Idee zu zeigen:
Dieses Beispiel zeigt, wie Sie Berechnungen mit Nebenwirkungen erstellen können (in diesem Beispiel
print
) und diese dann in Datenstrukturen einfügen oder auf andere Weise bearbeiten können, bevor Sie sie tatsächlich ausführen.quelle
call = x => x(); sequence_ = xs => xs.forEach(call) ;print = console.log; f = () => sequence_([()=> print(1), () => print(2), () => print(3)].reverse())
. Der Hauptunterschied, den ich sehe, ist, dass wir ein paar extra brauchen() =>
.