Wie würden Befürworter der funktionalen Programmierung diese Aussage in Code Complete beantworten?

30

Auf Seite 839 der zweiten Ausgabe erörtert Steve McConnell alle Möglichkeiten, wie Programmierer die Komplexität in großen Programmen "beherrschen" können. Seine Tipps gipfeln in dieser Aussage:

"Objektorientierte Programmierung bietet eine Abstraktionsebene, die gleichzeitig für Algorithmen und Daten gilt , eine Art von Abstraktion, die die funktionale Zerlegung allein nicht bot."

In Verbindung mit seiner Schlussfolgerung, dass "die Reduzierung der Komplexität wohl der wichtigste Schlüssel zu einem effektiven Programmierer ist" (dieselbe Seite), scheint dies eine echte Herausforderung für die funktionale Programmierung zu sein.

Die Debatte zwischen FP und OO wird häufig von FP-Befürwortern über die Komplexität geführt, die sich speziell aus den Herausforderungen der Parallelität oder Parallelisierung ergibt. Aber Parallelität ist sicherlich nicht die einzige Art von Komplexität, die Software-Programmierer erobern müssen. Wenn Sie sich vielleicht darauf konzentrieren, eine Art von Komplexität zu reduzieren, erhöht sich dies in anderen Dimensionen erheblich, sodass der Gewinn in vielen Fällen die Kosten nicht wert ist.

Wie würde diese Debatte aussehen, wenn wir die Vergleichsbedingungen zwischen FP und OO von bestimmten Aspekten wie Parallelität oder Wiederverwendbarkeit auf das Management globaler Komplexität verlagern würden?

BEARBEITEN

Der Kontrast, den ich hervorheben wollte, ist, dass OO die Komplexität sowohl von Daten als auch von Algorithmen zu kapseln und zu abstrahieren scheint, wohingegen die funktionale Programmierung zu ermutigen scheint, die Implementierungsdetails von Datenstrukturen im gesamten Programm "exponierter" zu lassen.

Siehe z. B. Stuart Halloway (ein Befürworter von Clojure FP), der hier sagt, dass "die Überbestimmung von Datentypen" eine "negative Folge des idiomatischen OO-Stils" ist und die Konzeption eines Adressbuchs als einfacher Vektor oder Karte anstelle eines reichhaltigeren OO-Objekts bevorzugt mit zusätzlichen (nicht vektoriellen und nicht kartenartigen) Eigenschaften und Methoden. (Auch OO- und Domain-Driven-Design-Befürworter können sagen, dass das Offenlegen eines Adressbuchs als Vektor oder Karte die eingekapselten Daten gegenüber Methoden überbelichtet, die vom Standpunkt der Domäne irrelevant oder sogar gefährlich sind.)

Dan
quelle
3
+1 Obwohl die Frage eher antagonistisch gerahmt wurde, ist es eine gute Frage.
Mattnz
16
Wie viele in den Antworten angegeben haben, sind funktionale Zerlegung und funktionale Programmierung zwei verschiedene Bestien. Die Schlussfolgerung, dass "dies eine ziemlich große Herausforderung für die funktionale Programmierung darstellt", ist eindeutig falsch und hat nichts damit zu tun.
Fabio Fracassi
5
Es ist klar, dass McConnels Wissen über die modernen funktionalen Datentypsysteme und erstklassigen Module etwas lückenhaft ist. Seine Aussage ist völliger Unsinn, da wir die erstklassigen Module und Funktoren (siehe SML) haben, Typklassen (siehe Haskell). Dies ist nur ein weiteres Beispiel dafür, dass OO mehr eine Religion als eine respektvolle Entwurfsmethode ist. Und woher haben Sie das mit der Nebenläufigkeit? Die meisten funktionalen Programmierer interessieren sich überhaupt nicht für die Parallelität.
SK-logic am
6
@ SK-logic Alles, was McConnell sagte, war, dass "funktionale Zerlegung allein" nicht die gleichen Abstraktionsmittel bietet wie OOP, was für mich eine ziemlich sichere Aussage ist. Nirgendwo sagt er, dass FP-Sprachen nicht so leistungsfähige Abstraktionsmittel haben wie OOP. Tatsächlich erwähnt er überhaupt keine FP-Sprachen. Das ist nur die Interpretation des OP.
12.
2
@ sepp2k, ok, ich verstehe. Dennoch kann ein sehr komplexes und gut geschichtetes System von Datenstrukturen und Verarbeitungsabstraktionen auf nichts als einer funktionalen Zerlegung für eine nahezu reine Lambda-Rechnung aufgebaut werden - indem das Verhalten der Module simuliert wird. Keine Notwendigkeit für die OO-Abstraktionen.
SK-logic am

Antworten:

13

Denken Sie daran, dass das Buch über 20 Jahre geschrieben wurde. Für professionelle Programmierer dieser Zeit existierte FP nicht - es befand sich ausschließlich im Bereich von Akademikern und Forschern.

Wir müssen die "funktionale Zerlegung" in den richtigen Kontext der Arbeit stellen. Der Autor bezieht sich nicht auf die funktionale Programmierung. Wir müssen dies auf die "strukturierte Programmierung" und das GOTOgefüllte Chaos zurückführen, das davor auftrat. Wenn Ihr Bezugspunkt ein altes FORTRAN / COBOL / BASIC ist, das keine Funktionen hatte (wenn Sie Glück hatten, könnten Sie eine einzige Ebene von GOSUB erhalten) und alle Ihre Variablen global sind, können Sie Ihr Programm auflösen in Schichten von Funktionen ist ein großer Segen.

OOP ist eine weitere Verfeinerung dieser Art von "funktionaler Zersetzung". Sie können nicht nur Anweisungen in Funktionen bündeln, sondern auch verwandte Funktionen mit den Daten gruppieren, an denen sie arbeiten. Das Ergebnis ist ein klar definierter Code, den Sie (im Idealfall) anzeigen und verstehen können, ohne die gesamte Codebasis nachverfolgen zu müssen, um herauszufinden, was sonst noch mit Ihren Daten zu tun hat.

Sean McSomething
quelle
27

Ich stelle mir vor, dass Befürworter der funktionalen Programmierung argumentieren würden, dass die meisten FP-Sprachen mehr Abstraktionsmittel als "funktionale Zerlegung allein" bieten und in der Tat Abstraktionsmittel zulassen, die leistungsmäßig mit denen objektorientierter Sprachen vergleichbar sind. Beispielsweise könnte man Haskells Typklassen oder MLs Module höherer Ordnung als solche Abstraktionsmittel anführen. Daher trifft die Aussage (bei der ich mir ziemlich sicher bin, dass es um Objektorientierung vs. prozedurale Programmierung und nicht um funktionale Programmierung geht) nicht auf sie zu.

Es sollte auch darauf hingewiesen werden, dass FP und OOP orthogonale Konzepte sind und sich nicht gegenseitig ausschließen. Es macht also keinen Sinn, sie miteinander zu vergleichen. Sie könnten sehr gut "imperative OOP" (z. B. Java) mit "funktionale OOP" (z. B. Scala) vergleichen, aber die von Ihnen angegebene Aussage würde für diesen Vergleich nicht gelten.

sepp2k
quelle
10
+1 "Funktionale Zerlegung"! = "Funktionale Programmierung". Der erste basiert auf der klassischen sequentiellen Codierung unter Verwendung von Vanille-Datenstrukturen ohne (oder nur von Hand gerollt) Vererbung, Einkapselung und Polymorphismus. Die zweite drückt Lösungen unter Verwendung von Lambda-Kalkül aus. Zwei völlig verschiedene Dinge.
Binary Worrier
4
Entschuldigung, aber der Ausdruck "prozedurale Programmierung" hat sich hartnäckig geweigert, früher in den Sinn zu kommen. "Funktionale Zerlegung" ist für mich weit mehr ein Hinweis auf prozedurale Programmierung als auf funktionale Programmierung.
Binary Worrier
Ja, du hast Recht. Ich bin davon ausgegangen, dass die funktionale Programmierung wiederverwendbare Funktionen bevorzugt, die immer wieder auf denselben einfachen Datenstrukturen (Listen, Bäumen, Karten) arbeiten, und behauptet sogar, dass dies ein Verkaufsargument gegenüber OO ist. Siehe Stuart Halloway (ein Befürworter von Clojure FP), der hier sagt, dass "die Überbestimmung von Datentypen" eine "negative Folge des idiomatischen OO-Stils" ist und die Konzeptualisierung eines Adressbuchs als Vektor oder Karte anstelle eines reichhaltigeren OO-Objekts mit anderen (nicht -vektorische und nicht kartenartige) Eigenschaften und Methoden.
Dan
Der Link für das Stuart Halloway-Zitat: thinkrelevance.com/blog/2009/08/12/…
dan
2
@dan So wird es vielleicht in der dynamisch getippten Sprache Clojure gemacht (ich weiß nicht, ich benutze Clojure nicht), aber ich denke, es ist gefährlich, daraus zu schließen, dass es in FP im Allgemeinen so gemacht wird. Haskell-Leute scheinen zum Beispiel sehr viel über abstrakte Typen und das Verbergen von Informationen zu wissen (vielleicht nicht so sehr wie Java-Leute).
13.
7

Ich finde die funktionale Programmierung beim Umgang mit Komplexität äußerst hilfreich. Sie tendieren jedoch dazu, Komplexität auf andere Weise zu betrachten und sie als Funktionen zu definieren, die auf unveränderlichen Daten auf verschiedenen Ebenen agieren, anstatt sie in einem OOP-Sinne zu kapseln.

Ich habe zum Beispiel kürzlich ein Spiel in Clojure geschrieben und der gesamte Status des Spiels wurde in einer einzigen unveränderlichen Datenstruktur definiert:

(def starting-game-state {:map ....
                          :player ....
                          :weather ....
                          :other-stuff ....}

Und die Hauptspielschleife könnte so definiert werden, dass sie einige reine Funktionen auf den Spielstatus in einer Schleife anwendet:

 (loop [initial-state starting-game-state]
   (let [user-input (get-user-input)
         game-state (update-game initial-state user-input)]
     (draw-screen game-state)
     (if-not (game-ended? game-state) (recur game-state))))

Die aufgerufene Schlüsselfunktion ist update-game, die einen Simulationsschritt ausführt, wenn ein vorheriger Spielstatus und Benutzereingaben vorliegen, und den neuen Spielstatus zurückgibt.

Wo ist die Komplexität? Meiner Meinung nach ist es ganz gut gelungen:

  • Sicherlich macht die Update-Spiel-Funktion eine Menge Arbeit, aber sie setzt sich selbst aus anderen Funktionen zusammen, so dass sie eigentlich ziemlich einfach ist. Sobald Sie ein paar Ebenen heruntergefahren haben, sind die Funktionen immer noch recht einfach: Sie können beispielsweise "Objekte zu Kartenkacheln hinzufügen".
  • Sicher ist der Spielstatus eine Big-Data-Struktur. Aber auch hier wird es nur durch das Zusammensetzen von Datenstrukturen auf niedrigerer Ebene aufgebaut. Außerdem handelt es sich um "reine Daten", anstatt dass Methoden eingebettet sind oder eine Klassendefinition erforderlich ist (Sie können es sich als ein sehr effizientes unveränderliches JSON-Objekt vorstellen, wenn Sie möchten), sodass nur sehr wenig Boilerplate vorhanden ist.

OOP kann auch Komplexität durch Kapselung verwalten. Vergleicht man dies jedoch mit OOP, hat das Funktionsprinzip einige sehr große Vorteile:

  • Die Spielzustandsdatenstruktur ist unveränderlich, so dass eine Vielzahl von Verarbeitungsvorgängen problemlos parallel ausgeführt werden kann. Zum Beispiel ist es absolut sicher, dass ein Rendering-Aufruf-Bildschirm in einem anderen Thread als der Spielelogik angezeigt wird - sie können sich möglicherweise nicht gegenseitig beeinflussen oder einen inkonsistenten Status anzeigen. Dies ist überraschend schwierig mit einem großen veränderlichen Objektgraphen.
  • Sie können jederzeit einen Schnappschuss des Spielstatus erstellen. Wiederholungen sind trivial (dank der beständigen Datenstrukturen von Clojure belegen die Kopien kaum Speicherplatz, da die meisten Daten gemeinsam genutzt werden). Sie können auch ein Update-Spiel ausführen, um "die Zukunft vorherzusagen", damit die KI beispielsweise verschiedene Züge bewerten kann.
  • Nirgendwo musste ich irgendwelche schwierigen Kompromisse eingehen, um mich in das OOP-Paradigma einzufügen, wie zum Beispiel die Definition einer starren Klassenerbschaft. In diesem Sinne verhält sich die funktionale Datenstruktur eher wie ein flexibles prototypbasiertes System.

Für Leute, die mehr über den Umgang mit Komplexität in funktionalen und OOP-Sprachen erfahren möchten, empfehle ich das Video von Rich Hickeys Keynote " Simple Made Easy" (gedreht auf der Strange Loop- Technologiekonferenz).

mikera
quelle
2
Ich denke, ein Spiel ist eines der schlechtesten Beispiele, um die angeblichen "Vorteile" einer erzwungenen Unveränderlichkeit zu demonstrieren. In einem Spiel bewegen sich die Dinge ständig, was bedeutet, dass Sie Ihren Spielstatus ständig neu aufbauen müssen. Und wenn alles unveränderlich ist, bedeutet dies, dass Sie nicht nur den Spielstatus neu erstellen müssen, sondern auch alles in der Grafik, das einen Verweis darauf enthält, oder das einen Verweis darauf enthält, und so weiter, bis Sie das Ganze recyceln Programm mit über 30 FPS, mit tonnenweise GC-Churn zum Booten! Es gibt keine Möglichkeit, eine gute Leistung daraus zu ziehen ...
Mason Wheeler
7
Natürlich sind Spiele schwer mit Unveränderlichkeit - deshalb habe ich sie gewählt, um zu demonstrieren, dass es immer noch funktionieren kann! Sie werden jedoch überrascht sein, was persistente Datenstrukturen bewirken können - der Großteil des Spielstatus muss nicht neu erstellt werden, sondern nur das, was sich ändert. Sicher, es ist etwas Overhead, aber es ist nur ein kleiner konstanter Faktor. Gib mir genug Kerne und ich werde deine Single-Threaded-C ++ - Game-Engine
besiegen
3
@Mason Wheeler: Tatsächlich ist es möglich, mit unveränderlichen Objekten praktisch die gleiche Leistung (wie mit Mutation) zu erzielen, ohne dass viel GC erforderlich ist . Der Trick in Clojure besteht darin, beständige Datenstrukturen zu verwenden : Sie sind für den Programmierer unveränderlich, aber tatsächlich veränderlich. Beste aus beiden Welten.
Joonas Pulakka
4
@quant_dev Mehr Kerne sind billiger als besser Kern ... escapistmagazine.com/news/view/...
deworde
6
@quant_dev - es ist keine Entschuldigung, es ist eine mathematische und architektonische Tatsache, dass Sie besser einen konstanten Overhead haben, wenn Sie dies wettmachen können, indem Sie Ihre Leistung nahezu linear mit der Anzahl der Kerne skalieren. Der Grund, warum funktionale Sprachen letztendlich eine überlegene Leistung bieten werden, ist, dass wir bei der Single-Core-Leistung an das Ende der Reihe gekommen sind und es in Zukunft um Parallelität und Parallelität gehen wird. Damit dies funktioniert, sind funktionale Ansätze (und insbesondere Unveränderlichkeit) wichtig.
mikera
3

"Objektorientierte Programmierung bietet eine Abstraktionsebene, die gleichzeitig für Algorithmen und Daten gilt, eine Art von Abstraktion, die die funktionale Zerlegung allein nicht bot."

Eine funktionale Zerlegung allein reicht nicht aus, um einen Algorithmus oder ein Programm zu erstellen: Sie müssen auch die Daten darstellen. Ich denke, die obige Aussage geht implizit davon aus (oder kann zumindest so verstanden werden), dass die "Daten" im funktionalen Fall von der rudimentärsten Art sind: nur Listen von Symbolen und sonst nichts. Das Programmieren in einer solchen Sprache ist offensichtlich nicht sehr bequem. Viele, insbesondere die neuen und modernen, funktionalen (oder multiparadigmen) Sprachen wie Clojure bieten jedoch umfangreiche Datenstrukturen: nicht nur Listen, sondern auch Zeichenfolgen, Vektoren, Karten und Mengen, Datensätze, Strukturen und Objekte! - Mit Metadaten und Polymorphismus.

Der enorme praktische Erfolg von OO-Abstraktionen ist kaum zu bestreiten. Aber ist es das letzte Wort? Wie Sie geschrieben haben, sind Nebenläufigkeitsprobleme bereits das größte Problem, und das klassische OO enthält überhaupt keine Vorstellung von Nebenläufigkeit. Daher sind die OO-Lösungen für den Umgang mit Nebenläufigkeiten de facto nur überlagertes Klebeband: Sie funktionieren, sind jedoch leicht zu beschädigen, nehmen der eigentlichen Aufgabe beträchtliche Mengen an Gehirnressourcen ab und lassen sich nicht gut skalieren. Vielleicht ist es möglich, das Beste aus vielen Welten zu holen. Das ist, was moderne Multiparadigmasprachen verfolgen.

Joonas Pulakka
quelle
1
Ich habe irgendwo den Satz "OO im Großen, FP im Kleinen" gehört - ich glaube, Michael Feathers hat ihn zitiert. Das bedeutet, dass FP für bestimmte Teile eines großen Programms gut sein kann, aber im Allgemeinen sollte es OO sein.
Dan
Anstatt Clojure für alles zu verwenden, auch für Dinge, die in traditionellerer OO-Syntax klarer ausgedrückt werden, sollten Sie Clojure für die Datenverarbeitungsbits verwenden, die sauberer sind, und Java oder eine andere OO-Sprache für die anderen Bits. Polyglot-Programmierung statt Multiparadigm-Programmierung mit derselben Sprache für alle Teile des Programms. (So ​​ähnlich wie die meisten Webanwendungen SQL und OO für verschiedene Ebenen verwenden.)
Dan
@dan: Benutze das Werkzeug, das am besten zum Job passt. Bei der Polyglot-Programmierung ist die bequeme Kommunikation zwischen den Sprachen der entscheidende Faktor, und Clojure und Java könnten kaum besser zusammenspielen . Ich glaube, die meisten umfangreichen Clojure-Programme verwenden hier und da mindestens einige Bits der Standard-Java-Bibliotheken von JDK.
Joonas Pulakka
2

Der veränderbare Zustand ist die Wurzel der meisten Komplexitäten und Probleme im Zusammenhang mit der Programmierung und dem Entwurf von Software / Systemen.

OO umfasst veränderlichen Zustand. FP verabscheut veränderlichen Zustand.

Sowohl OO als auch FP haben ihre Verwendungszwecke und Sweet Spots. Mit Bedacht wählen. Und denken Sie an das Sprichwort: "Verschlüsse sind Gegenstände des armen Mannes. Gegenstände sind Verschlüsse des armen Mannes."

Maglob
quelle
3
Ich bin nicht sicher, ob Ihre Eröffnungsbehauptung wahr ist. Die Wurzel der "meisten" Komplexitäten? In der Programmierung, die ich gemacht oder gesehen habe, ist das Problem weniger ein veränderlicher Zustand als vielmehr ein Mangel an Abstraktion und eine Überfülle an Details durch den Code.
Dan
1
@ Dan: Interessant. Tatsächlich habe ich viel Gegenteiliges gesehen: Probleme ergeben sich aus übermäßigem Gebrauch von Abstraktion, was es schwierig macht, die Details der tatsächlichen Vorgänge zu verstehen und erforderlichenfalls zu korrigieren.
Mason Wheeler
1

Funktionale Programmierung kann Objekte haben, aber diese Objekte neigen dazu, unveränderlich zu sein. Reine Funktionen (Funktionen ohne Nebenwirkungen) arbeiten dann mit diesen Datenstrukturen. Es ist möglich, unveränderliche Objekte in objektorientierten Programmiersprachen zu erstellen, aber sie wurden nicht dafür entwickelt, und so werden sie normalerweise nicht verwendet. Dies macht es schwierig, über objektorientierte Programme nachzudenken.

Nehmen wir ein sehr einfaches Beispiel. Angenommen, Oracle hat entschieden, dass Java Strings eine umgekehrte Methode haben soll, und Sie haben den folgenden Code geschrieben.

String x = "abc";
StringBuffer y = new StringBuffer(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString());

Was wertet die letzte Zeile aus? Sie benötigen spezielle Kenntnisse der String-Klasse, um zu wissen, dass diese als falsch ausgewertet wird.

Was wäre, wenn ich meine eigene Klasse WuHoString machen würde?

String x = "abc";
WuHoString y = new WuHoString(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString())

Es ist unmöglich zu wissen, was die letzte Zeile ergibt.

In einem funktionalen Programmierstil würde es mehr wie folgt geschrieben werden:

String x;
equals(toString(reverse(x)), toString(reverse(WuHoString(x))))

und es sollte wahr sein.

Wenn eine Funktion in einer der grundlegendsten Klassen so schwer zu erklären ist, fragt man sich, ob die Einführung dieser Idee von veränderlichen Objekten die Komplexität erhöht oder verringert hat.

Offensichtlich gibt es alle möglichen Definitionen dessen, was objektorientiert ist und was es bedeutet, funktional zu sein und was es bedeutet, beides zu haben. Für mich kann man einen "funktionalen Programmierstil" in der Sprache haben, der keine erstklassigen Funktionen hat, sondern für den andere Sprachen gemacht sind.

WuHoUnited
quelle
3
Es ist ein bisschen komisch, dass Sie sagen, dass OO-Sprachen nicht für unveränderliche Objekte erstellt wurden, und später ein Beispiel mit Zeichenfolgen verwenden (die in den meisten OO-Sprachen, einschließlich Java, unveränderlich sind). Ich möchte auch darauf hinweisen, dass es OO-Sprachen (oder vielmehr Multi-Paradigma-Sprachen) gibt, deren Schwerpunkt auf unveränderlichen Objekten liegt (zum Beispiel Scala).
12.
@ sepp2k: Gewöhne dich daran. FP-Befürworter werfen immer wieder bizarre, erfundene Beispiele auf, die nichts mit realer Codierung zu tun haben. Nur so können Kern-FP-Konzepte wie erzwungene Unveränderlichkeit gut aussehen.
Mason Wheeler
1
@Mason: Huh? Wäre es nicht die beste Möglichkeit, die Unveränderlichkeit gut aussehen zu lassen, zu sagen, dass Java (und C #, Python usw.) unveränderliche Zeichenfolgen verwendet und dass es großartig funktioniert?
12.
1
@ sepp2k: Wenn unveränderliche Zeichenfolgen so gut funktionieren, warum werden StringBuilder- / StringBuffer-Stilklassen immer wieder angezeigt? Es ist nur ein weiteres Beispiel für eine Abstraktionsinversion, die Ihnen im Weg steht.
Mason Wheeler
2
In vielen objektorientierten Sprachen können Sie unveränderliche Objekte erstellen. Aber das Konzept, die Methoden an die Klasse zu binden, entmutigt sie aus meiner Sicht wirklich. Das String-Beispiel ist eigentlich kein künstliches Beispiel. Jedes Mal, wenn ich in Java eine Methode aufrufe, gehe ich eine Chance ein, ob meine Parameter innerhalb dieser Funktion mutiert werden.
WuHoUnited
0

Ich denke, in den meisten Fällen deckt die klassische OOP-Abstraktion die Komplexität der Parallelität nicht ab. Daher schließt OOP (nach seiner ursprünglichen Bedeutung) FP nicht aus, und deshalb sehen wir Dinge wie Scala.

duyt
quelle
0

Die Antwort hängt von der Sprache ab. Lisps, zum Beispiel, haben die wirklich ordentlich richtig , dass Code ist Daten - die Algorithmen schreiben Sie sind eigentlich nur Listen Lisp! Sie speichern Daten genauso, wie Sie das Programm schreiben. Diese Abstraktion ist gleichzeitig einfacher und gründlicher als OOP und lässt Sie einige wirklich nette Dinge tun (siehe Makros).

Haskell (und eine ähnliche Sprache, wie ich mir vorstelle) haben eine ganz andere Antwort: algebraische Datentypen. Ein algebraischer Datentyp ist wie aC Struktur, bietet jedoch mehr Optionen. Diese Datentypen stellen die Abstraktion bereit, die zum Modellieren von Daten erforderlich ist. Funktionen liefern die zur Modellierung von Algorithmen erforderliche Abstraktion. Typklassen und andere erweiterte Funktionen bieten eine noch höhere Abstraktionsebene für beide.

Zum Beispiel arbeite ich aus Spaß an einer Programmiersprache namens TPL. Algebraische Datentypen machen es wirklich einfach Werte zu repräsentieren:

data TPLValue = Null
              | Number Integer
              | String String
              | List [TPLValue]
              | Function [TPLValue] TPLValue
              -- There's more in the real code...

Dies besagt auf sehr visuelle Weise, dass ein TPLValue (jeder Wert in meiner Sprache) ein Nulloder ein sein kannNumber mit einem IntegerWert oder sogar Functioneine Liste von Werten (die Parameter) und ein Endwert (der Körper) sein kann ).

Als nächstes kann ich Typklassen verwenden, um ein allgemeines Verhalten zu kodieren. Zum Beispiel könnte ich machenTPLValue Instanz Showerstellen, die bedeutet, dass sie in eine Zeichenfolge konvertiert werden kann.

Außerdem kann ich meine eigenen Typklassen verwenden, wenn ich das Verhalten bestimmter Typen angeben muss (einschließlich solcher, die ich nicht selbst implementiert habe). Zum Beispiel habe ich eine ExtractableTypklasse, mit der ich eine Funktion schreiben kann, die a annimmt TPLValueund einen entsprechenden normalen Wert zurückgibt. Somit extractkann man a Numberzu a Integeroder a Stringzu a Stringsolange konvertierenInteger und StringInstanzen von Extractable.

Schließlich ist die Hauptlogik meines Programms in mehreren Funktionen wie evalundapply . Dies ist wirklich der Kern - sie nehmen TPLValues und verwandeln sie in mehr TPLValues sowie den Umgang mit Status und Fehlern.

Insgesamt sind die Abstraktionen ich benutze in meinem Haskell Code tatsächlich mehr leistungsfähig als das, was ich in einer OOP - Sprache verwendet haben.

Tikhon Jelvis
quelle
Ja, ich muss lieben eval. "Hey, sieh mich an! Ich muss nicht meine eigenen Sicherheitslücken schreiben. Ich habe eine willkürliche Sicherheitslücke, die direkt in die Programmiersprache eingebaut ist!" Das Verknüpfen von Daten mit Code ist die Hauptursache für eine der beiden beliebtesten Sicherheitslücken aller Zeiten. Jedes Mal, wenn Sie sehen, dass jemand wegen eines SQL-Injection-Angriffs (unter anderem) gehackt wird, weiß ein Programmierer nicht, wie er Daten richtig vom Code trennen kann.
Mason Wheeler
evalhängt nicht sehr von der Struktur von Lisp ab - Sie können es evalin Sprachen wie JavaScript und Python haben. Die wahre Kraft liegt im Schreiben von Makros, also Programmen, die auf Programme wie Daten einwirken und andere Programme ausgeben. Dies macht die Sprache sehr flexibel und das Erstellen leistungsfähiger Abstraktionen einfach.
Tikhon Jelvis
3
Ja, ich habe schon oft gehört, dass Makros großartig sind. Ich habe jedoch noch nie ein tatsächliches Beispiel für ein Lisp-Makro gesehen, das etwas ausführt, das 1) praktisch ist und das Sie eigentlich gerne im Code der realen Welt ausführen würden, und 2) das in keiner Sprache, die Funktionen unterstützt, genauso einfach ausgeführt werden kann.
Mason Wheeler
1
@ MasonWheeler-Kurzschluss and. kurzschließen or. let. let-rec. cond. defn. Keines davon kann mit Funktionen in anwendbaren Auftragssprachen implementiert werden. for(Listenverständnisse). dotimes. doto.
1
@MattFenwick: OK, ich hätte wirklich einen dritten Punkt zu meinen beiden obigen hinzufügen sollen: 3) ist in keiner vernünftigen Programmiersprache bereits integriert. Weil das die einzigen wirklich nützlichen Makrobeispiele sind, die ich jemals sehe, und wenn Sie sagen "Hey, schau mich an, meine Sprache ist so flexibel, dass ich meinen eigenen Kurzschluss implementieren kann and!" Ich höre: "Hey schau mich an, meine Sprache ist so verkrüppelt, dass es nicht einmal zu einem Kurzschluss kommt andund ich das Rad für alles neu erfinden muss !"
Mason Wheeler
0

Der zitierte Satz hat meines Erachtens keine Gültigkeit mehr.

Zeitgenössische OO-Sprachen können nicht über Typen abstrahieren, deren Typ nicht * ist, dh Typen höherer Typen sind unbekannt. Ihr Typensystem erlaubt es nicht, die Idee von "einem Container mit Int-Elementen, der es erlaubt, eine Funktion über die Elemente abzubilden" auszudrücken.

Daher funktioniert diese Grundfunktion wie Haskells

fmap :: Functor f => (a -> b) -> f a -> f b 

kann nicht einfach in Java *) geschrieben werden, zumindest nicht typsicher. Um grundlegende Funktionen zu erhalten, müssen Sie daher eine Menge Boilerplate schreiben, da Sie benötigen

  1. Eine Methode zum Anwenden einer einfachen Funktion auf Elemente einer Liste
  2. Eine Methode, um die gleiche einfache Funktion auf Elemente eines Arrays anzuwenden
  3. eine Methode, um die gleiche einfache Funktion auf Werte eines Hash anzuwenden,
  4. .... einstellen
  5. .... Baum
  6. ... 10. Unit-Tests für das gleiche

Und doch sind diese fünf Methoden im Grunde derselbe Code, geben oder nehmen einige. Im Gegensatz dazu würde ich in Haskell brauchen:

  1. Eine Functor-Instanz für Liste, Array, Map, Set und Tree (meist vordefiniert oder kann vom Compiler automatisch abgeleitet werden)
  2. die einfache Funktion

Beachten Sie, dass sich dies mit Java 8 nicht ändern wird (nur, dass man Funktionen einfacher anwenden kann, aber dann genau das oben genannte Problem auftritt. Solange Sie nicht einmal Funktionen höherer Ordnung haben, sind Sie höchstwahrscheinlich nicht einmal verstehen, wofür höherwertige Typen gut sind.)

Selbst neue OO-Sprachen wie Ceylon haben keine höherwertigen Typen. (Ich habe kürzlich Gavin King gefragt, und er sagte mir, es sei zu diesem Zeitpunkt nicht wichtig.) Ich weiß jedoch nichts über Kotlin.

*) Um fair zu sein, können Sie ein Interface Functor haben, das eine Methode fmap hat. Schlimm ist, dass man nicht sagen kann: Hey, ich weiß, wie man fmap für die Bibliotheksklasse SuperConcurrentBlockedDoublyLinkedDequeHasMap implementiert.

Ingo
quelle
FTR: die Ceylon typechecker und JavaScript Backend jetzt tun Unterstützung höher handed - Typen (und auch höheren Rang Typen). Es wird als "experimentelles" Feature angesehen. Unsere Community hat jedoch Probleme damit, praktische Anwendungen für diese Funktionalität zu finden. Daher ist es eine offene Frage, ob dies jemals ein "offizieller" Teil der Sprache sein wird. Ich gehe davon aus, dass es irgendwann vom Java-Backend unterstützt wird.
Gavin King
-2

Jeder, der jemals in dBase programmiert hat, würde wissen, wie nützlich einzeilige Makros für die Erstellung von wiederverwendbarem Code sind. Obwohl ich nicht in Lisp programmiert habe, habe ich von vielen anderen gelesen, die auf Makros zur Kompilierungszeit schwören. Die Idee, Code zur Kompilierzeit in Ihren Code einzufügen, wird in jedem C-Programm mit der Anweisung "include" auf einfache Weise verwendet. Weil Lisp dies mit einem Lisp-Programm tun kann und weil Lisp sehr reflektierend ist, erhalten Sie viel flexiblere Includes.

Jeder Programmierer, der nur einen beliebigen Text aus dem Internet entnimmt und an seine Datenbank weiterleitet, ist kein Programmierer. Ebenso ist jeder, der zulässt, dass "Benutzer" -Daten automatisch zu ausführbarem Code werden, offensichtlich dumm. Das bedeutet nicht, dass es eine schlechte Idee ist, Programmen zu erlauben, Daten zur Ausführungszeit zu manipulieren und sie dann als Code auszuführen. Ich glaube, dass diese Technik in Zukunft unverzichtbar sein wird, da "intelligenter" Code die meisten Programme tatsächlich schreibt. Das ganze "Daten / Code-Problem" oder nicht ist eine Frage der Sicherheit in der Sprache.

Eines der Probleme mit den meisten Sprachen ist, dass sie für eine einzelne Offline-Person erstellt wurden, um einige Funktionen für sich selbst auszuführen. Für reale Programme ist es erforderlich, dass viele Benutzer jederzeit und gleichzeitig von mehreren Kernen und mehreren Computerclustern aus Zugriff haben. Sicherheit sollte ein Teil der Sprache sein und nicht des Betriebssystems, und in nicht allzu ferner Zukunft wird es sein.

David Clark
quelle
2
Willkommen bei den Programmierern. Bitte überlegen Sie, die Rhetorik in Ihrer Antwort abzuschwächen und einige Ihrer Behauptungen mit externen Referenzen zu untermauern.
1
Jeder Programmierer, der zulässt, dass Benutzerdaten automatisch zu ausführbarem Code werden, ist offensichtlich unwissend . Nicht dumm. Dies zu tun ist oft einfach und offensichtlich, und wenn sie nicht wissen, warum es eine schlechte Idee ist und dass es eine bessere Lösung gibt, können Sie ihnen nicht wirklich die Schuld dafür geben. (Jeder, der dies tut, nachdem er gelernt hat, dass es einen besseren Weg gibt, ist offensichtlich dumm.)
Mason Wheeler