Ich habe den Artikel hier gelesen: http://www.paulgraham.com/avg.html und der Teil über das "Blub-Paradoxon" war besonders interessant. Als jemand, der hauptsächlich in c ++ codiert, aber mit anderen Sprachen in Berührung kommt (hauptsächlich Haskell), sind mir einige nützliche Dinge in diesen Sprachen bekannt, die in c ++ schwer zu replizieren sind. Die Frage richtet sich hauptsächlich an Leute, die sowohl mit C ++ als auch mit einer anderen Sprache vertraut sind. Gibt es ein mächtiges Sprachmerkmal oder eine Sprache, die Sie in einer Sprache verwenden, die schwer zu konzipieren oder zu implementieren wäre, wenn Sie nur in C ++ schreiben würden?
Insbesondere dieses Zitat erregte meine Aufmerksamkeit:
Die einzigen Programmierer, die in der Lage sind, alle Machtunterschiede zwischen den verschiedenen Sprachen zu erkennen, sind diejenigen, die die mächtigsten verstehen. (Dies ist wahrscheinlich, was Eric Raymond damit gemeint hat, dass Lisp Sie zu einem besseren Programmierer gemacht hat.) Sie können den Meinungen der anderen aufgrund des Blub-Paradoxons nicht vertrauen: Sie sind mit der Sprache, die sie gerade verwenden, zufrieden, weil sie die Sprache diktiert wie sie über Programme denken.
Wenn sich herausstellt, dass ich aufgrund der Verwendung von c ++ das Äquivalent zum "Blub" -Programmierer bin, wirft dies die folgende Frage auf: Gibt es nützliche Konzepte oder Techniken, die Sie in anderen Sprachen angetroffen haben, die Sie ohne Sie nur schwer zu konzipieren gefunden hätten? in c ++ geschrieben oder "nachgedacht"?
Zum Beispiel kann das Logikprogrammierparadigma, das in Sprachen wie Prolog und Mercury zu finden ist, mit der Castor-Bibliothek in c ++ implementiert werden, aber letztendlich stelle ich fest, dass ich konzeptionell in Prolog-Code denke und in das c ++ -Äquivalent übersetze, wenn ich diesen verwende. Um meine Programmierkenntnisse zu erweitern, versuche ich herauszufinden, ob es ähnliche Beispiele für nützliche / leistungsfähige Redewendungen gibt, die effizienter in anderen Sprachen ausgedrückt werden, die ich als C ++ - Entwickler möglicherweise nicht kenne. Ein weiteres Beispiel, das mir einfällt, ist das Makrosystem in lisp. Das Generieren des Programmcodes aus dem Programm heraus scheint für einige Probleme viele Vorteile zu haben. Dies scheint schwer zu implementieren und aus c ++ heraus zu überlegen.
Diese Frage ist nicht dazu gedacht, eine "c ++ vs lisp" -Debatte oder eine Art von Debatte über Sprachkriege zu sein. Wenn ich so eine Frage stelle, kann ich nur so Dinge herausfinden, von denen ich nichts weiß und von denen ich nichts weiß.
quelle
there are things that other languages can do that Lisp can't
- Unwahrscheinlich, da Lisp vollständig ist. Vielleicht wollten Sie damit sagen, dass es einige Dinge gibt , die in Lisp nicht praktikabel sind ? Ich könnte das Gleiche über jede Programmiersprache sagen .Antworten:
Nun, seit du Haskell erwähnt hast:
Mustervergleich. Ich finde Pattern Matching viel einfacher zu lesen und zu schreiben. Betrachten Sie die Definition der Karte und überlegen Sie, wie sie in einer Sprache ohne Mustervergleich implementiert werden würde.
Das Typensystem. Es kann manchmal ein Schmerz sein, aber es ist äußerst hilfreich. Sie müssen damit programmieren, um es wirklich zu verstehen und wie viele Bugs es fängt. Auch referentielle Transparenz ist wunderbar. Erst nach einer Weile der Programmierung in Haskell wird ersichtlich, wie viele Fehler durch das Verwalten des Status in einer imperativen Sprache verursacht werden.
Funktionsprogrammierung allgemein. Verwenden von Karten und Falten anstelle von Iterationen. Rekursion. Es geht darum, auf einer höheren Ebene zu denken.
Faule Bewertung. Auch hier geht es darum, auf einer höheren Ebene zu denken und das System die Evaluierung durchführen zu lassen.
Kabale, Pakete und Module. Das Herunterladen von Cabal-Paketen ist für mich viel praktischer als das Auffinden von Quellcode, das Schreiben eines Makefiles usw. Es ist viel besser, nur bestimmte Namen importieren zu können, als alle Quelldateien zusammen zu haben und dann zu kompilieren.
quelle
Maybe
(für C ++ siehestd::optional
), Dinge explizit als optional / nullable / maybe zu markieren.Merke dir!
Versuchen Sie es in C ++ zu schreiben. Nicht mit C ++ 0x.
Zu umständlich? Okay, versuche es mit C ++ 0x.
Sehen Sie, ob Sie diese 4-zeilige (oder 5-zeilige, was auch immer: P) Version zur Kompilierungszeit in D schlagen können :
Alles, was Sie tun müssen, um es anzurufen, ist so etwas wie:
Sie können auch etwas Ähnliches in Scheme ausprobieren, obwohl es etwas langsamer ist, weil es zur Laufzeit passiert und weil die Suche hier linear statt gehasht ist (und nun, weil es Scheme ist):
quelle
C ++ ist eine multiparadigmische Sprache, die versucht, viele Denkweisen zu unterstützen. Manchmal ist eine C ++ - Funktion umständlicher oder weniger fließend als die Implementierung einer anderen Sprache, wie dies bei der funktionalen Programmierung der Fall ist.
Das heißt, ich kann mir nicht vorstellen, was
yield
in einer nativen C ++ - Sprachfunktion alles in Python oder JavaScript steckt.Ein weiteres Beispiel ist die gleichzeitige Programmierung . C ++ 0x wird darüber ein Mitspracherecht haben, der aktuelle Standard jedoch nicht, und Parallelität ist eine völlig neue Denkweise.
Auch die schnelle Entwicklung - selbst die Shell-Programmierung - werden Sie nie lernen, wenn Sie die Domäne der C ++ - Programmierung nie verlassen.
quelle
setjmp
und ... verwendenlongjmp
. Ich habe keine Ahnung, wie viel das bricht, aber ich denke, Ausnahmen wären die ersten, die gehen. Nun, wenn Sie mich entschuldigen, ich muss Modern C ++ Design noch einmal lesen , um das aus meinem Kopf zu bekommen.Coroutinen sind eine immens nützliche Sprachfunktion , die viele der greifbareren Vorteile anderer Sprachen gegenüber C ++ untermauert. Sie stellen im Grunde genommen zusätzliche Stapel zur Verfügung, damit Funktionen unterbrochen und fortgesetzt werden können, und stellen der Sprache pipelineähnliche Funktionen zur Verfügung, mit denen die Ergebnisse von Operationen problemlos über Filter an andere Operationen übertragen werden können. Es ist wunderbar und in Ruby fand ich es sehr intuitiv und elegant. Daran knüpft auch die faule Bewertung an.
Kompilierung / Ausführung / Auswertung von Introspection- und Laufzeit-Code / was auch immer sind mächtige Features, die C ++ fehlt.
quelle
Nachdem ich sowohl in Lisp als auch in C ++ ein Computer-Algebra-System implementiert habe, kann ich Ihnen sagen, dass die Aufgabe in Lisp viel einfacher war, obwohl ich ein absoluter Anfänger in der Sprache war. Diese Vereinfachung aller Listen vereinfacht eine Vielzahl von Algorithmen. Zugegeben, die C ++ - Version war zig Mal schneller. Ja, ich hätte die Lisp-Version schneller machen können, aber der Code wäre nicht so lispig. Scripting ist eine weitere Sache, die immer einfacher wird, zum Beispiel Lisp. Es geht darum, das richtige Werkzeug für den Job zu verwenden.
quelle
Was meinen wir, wenn wir sagen, dass eine Sprache "mächtiger" ist als eine andere? Wenn wir sagen, dass eine Sprache "expressiv" ist? Oder "reich?" Ich denke, wir meinen, dass eine Sprache an Macht gewinnt, wenn ihr Sichtfeld so eng wird, dass es einfach und natürlich ist, ein Problem zu beschreiben - wirklich ein Zustandsübergang, nicht wahr? - das lebt in dieser Sicht. Diese Sprache ist jedoch bedeutend weniger mächtig, weniger ausdrucksstark und weniger nützlich, wenn sich unser Sichtfeld erweitert.
Je "kraftvoller" und "ausdrucksvoller" die Sprache ist, desto begrenzter ist ihre Verwendung. Vielleicht sind "kraftvoll" und "ausdrucksstark" die falschen Worte für ein Werkzeug von engem Nutzen. Vielleicht sind "angemessen" oder "abstrakt" bessere Worte für solche Dinge.
Ich habe mit dem Programmieren angefangen, indem ich eine ganze Reihe von Sachen auf niedriger Ebene geschrieben habe: Gerätetreiber mit ihren Interrupt-Routinen; eingebettete Programme; Betriebssystemcode. Der Code war eng mit der Hardware verbunden und ich schrieb alles in Assemblersprache. Wir würden nicht sagen, dass Assembler am wenigsten abstrakt ist, aber es war und ist die mächtigste und ausdrucksstärkste Sprache von allen. Ich kann jedes Problem in Assemblersprache ausdrücken. Es ist so mächtig, dass ich mit jeder Maschine alles machen kann, was ich will.
Und mein späteres Verständnis der höheren Sprache verdankt alles meiner Erfahrung mit Assembler. Alles, was ich später gelernt habe, war einfach, denn alles - egal wie abstrakt - muss sich am Ende der Hardware anpassen.
Möglicherweise möchten Sie immer höhere Abstraktionsebenen, dh immer engere Sichtfelder, vergessen. Sie können das später jederzeit wieder aufnehmen. Es ist ein Kinderspiel zu lernen, eine Frage von Tagen. Meiner Meinung nach ist es besser, die Sprache von Hardware 1 zu lernen , um so nah wie möglich am Knochen zu sein.
1 Vielleicht nicht ganz deutsch, aber
car
undcdr
nehmen Sie ihre Namen von der Hardware: Die erste Lisp lief auf einem Computer, der ein tatsächliches Dekrementregister und ein tatsächliches Adressregister hatte. Wie wäre es damit?quelle
Assoziative Arrays
Eine typische Art der Datenverarbeitung ist:
Das richtige Werkzeug dafür ist das assoziative Array .
Ich mag die assoziative Array-Syntax von JavaScript nicht wirklich, weil ich nicht erstellen kann, sagen wir a [x] [y] [z] = 8. Zuerst muss ich ein [x] und ein [x] [y] erstellen .
Okay, in C ++ (und in Java) gibt es ein nettes Portfolio an Container-Klassen, Map , Multimap , aber wenn ich mich umsehen will, muss ich einen Iterator erstellen, und wenn ich ein neues Deep-Level-Element einfügen will, möchte ich müssen alle oberen Ebenen usw. schaffen. Unbequem.
Ich sage nicht, dass es in C ++ (und Java) keine verwendbaren assoziativen Arrays gibt, aber typenlose (oder nicht streng typisierte) Skriptsprachen schlagen kompilierte, weil sie typenlose Skriptsprachen sind.
Haftungsausschluss: Ich bin nicht mit C # und anderen .NET-Sprachen vertraut.
quelle
dict
Typ verwenden (x = {0: 5, 1: "foo", None: 500e3}
beachten Sie beispielsweise , dass Schlüssel oder Werte nicht vom selben Typ sein müssen). Der Versuch, so etwas zu tun,a[x][y][z] = 8
ist schwierig, da die Sprache in die Zukunft schauen muss, um zu sehen, ob Sie einen Wert festlegen oder eine andere Ebene erstellen werden. der Ausdrucka[x][y]
an sich sagt es dir nicht.Ich lerne nicht Java, C \ C ++, Assembly und Java Script. Ich benutze C ++, um meinen Lebensunterhalt zu verdienen.
Allerdings würde ich sagen, dass ich die Assembly- und C-Programmierung mehr mag. Dies ist hauptsächlich in Zusammenhang mit der imperativen Programmierung zu sehen.
Ich weiß, dass Programmierparadigmen wichtig sind, um Datentypen zu kategorisieren und abstrahierte Konzepte für eine höhere Programmierstufe bereitzustellen, um leistungsfähige Entwurfsmuster und die Formalisierung von Code zu ermöglichen. In gewissem Sinne ist jedes Paradigma eine Sammlung von Mustern und Sammlungen, um die zugrunde liegende Hardwareschicht zu abstrahieren, sodass Sie nicht über den EAX oder die interne IP innerhalb der Maschine nachdenken müssen.
Mein einziges Problem dabei ist, dass die Vorstellung und die Konzepte der Menschen, wie die Maschine funktioniert, in Ideologien und zweideutige Behauptungen darüber, was vor sich geht, umgewandelt werden können. Dieses Brot enthält alle Arten von wunderbaren Abstraktionen, die zu einem ideologischen Ziel des Programmierers passen.
Am Ende des Tages ist es besser, eine klare Einstellung zu haben und die Grenzen der CPU zu kennen und zu wissen, wie Computer unter der Haube funktionieren. Die CPU führt lediglich eine Reihe von Befehlen aus, die Daten in ein Register hinein und aus dem Speicher heraus bewegen und einen Befehl ausführen. Es gibt kein Konzept für den Datentyp oder höhere Programmierkonzepte. Es werden nur Daten verschoben.
Es wird komplexer, wenn Sie der Mischung Programmierparadigmen hinzufügen, weil unsere Sicht der Welt alle unterschiedlich ist.
quelle
C ++ macht viele Ansätze unlösbar. Ich würde sogar sagen, dass der größte Teil der Programmierung schwer zu verstehen ist, wenn Sie sich auf C ++ beschränken. Hier einige Beispiele für Probleme, die sich auf eine Weise lösen lassen, die C ++ erschwert.
Registrieren Sie Zuordnungs- und Anrufkonventionen
Viele Leute halten C ++ für eine Bare-Metal-Low-Level-Sprache, aber das ist es nicht. Indem wichtige Details der Maschine entfernt werden, ist es in C ++ schwierig, praktische Aspekte wie die Zuweisung von Registern und Aufrufkonventionen zu konzipieren.
Um mehr über solche Konzepte zu erfahren, empfehle ich, etwas Assembler-Programmierung zu probieren und diesen Artikel über die Qualität der ARM-Codegenerierung zu lesen .
Laufzeitcode-Generierung
Wenn Sie nur C ++ kennen, denken Sie wahrscheinlich, dass Vorlagen das A und O der Metaprogrammierung sind. Sie sind nicht. Tatsächlich sind sie ein objektiv schlechtes Werkzeug für die Metaprogrammierung. Jedes Programm, das ein anderes Programm manipuliert, ist ein Metaprogramm, einschließlich Interpreter, Compiler, Computeralgebrasysteme und Theorembeweiser. Die Generierung von Laufzeitcode ist hierfür eine nützliche Funktion.
Ich empfehle, eine Schema-Implementierung
EVAL
zu starten und damit zu spielen , um mehr über die metazirkuläre Evaluierung zu erfahren.Bäume manipulieren
Bäume sind überall in der Programmierung. Beim Parsen haben Sie abstrakte Syntaxbäume. In Compilern haben Sie IRs, die Bäume sind. In der Grafik- und GUI-Programmierung haben Sie Szenenbäume.
Dieser "Lächerlich einfache JSON-Parser für C ++" wiegt nur 484 LOC, was für C ++ sehr klein ist. Vergleichen Sie es jetzt mit meinem eigenen einfachen JSON-Parser, der nur 60 LOC F # wiegt. Der Unterschied liegt in erster Linie darin, dass die algebraischen Datentypen und der Mustervergleich (einschließlich aktiver Muster) von ML die Bearbeitung von Bäumen erheblich vereinfachen.
Schauen Sie sich auch in OCaml rot-schwarze Bäume an .
Rein funktionale Datenstrukturen
Das Fehlen von GC in C ++ macht es praktisch unmöglich, einige nützliche Ansätze zu übernehmen. Rein funktionale Datenstrukturen sind ein solches Werkzeug.
Schauen Sie sich beispielsweise diesen 47-zeiligen regulären Ausdrucksvergleich in OCaml an. Die Kürze ist weitgehend auf die weitgehende Verwendung rein funktionaler Datenstrukturen zurückzuführen. Insbesondere die Verwendung von Wörterbüchern mit Schlüsseln, die gesetzt sind. Dies ist in C ++ sehr schwierig, da die stdlib-Wörterbücher und -Sets alle veränderbar sind, Sie jedoch die Schlüssel eines Wörterbuchs nicht ändern können oder die Auflistung auflösen.
Logikprogrammierung und Rückgängig-Puffer sind weitere praktische Beispiele, bei denen rein funktionale Datenstrukturen in anderen Sprachen etwas erschweren, das in C ++ wirklich einfach ist.
Schwanz ruft
Nicht nur, dass C ++ keine Tail Calls garantiert, RAII widerspricht dem grundsätzlich, da Destruktoren einem Call in Tail Position im Wege stehen. Mit Tail-Aufrufen können Sie eine unbegrenzte Anzahl von Funktionsaufrufen mit nur begrenztem Stapelspeicher durchführen. Dies eignet sich hervorragend für die Implementierung von Zustandsautomaten, einschließlich erweiterbarer Zustandsautomaten, und ist in vielen ansonsten ungünstigen Situationen eine hervorragende Karte für den Ausstieg aus dem Gefängnis.
Sehen Sie sich zum Beispiel diese Implementierung des 0-1-Knapsack-Problems mit Continuation-Passing-Stil und Memoisierung in F # aus der Finanzbranche an. Wenn Sie Tail Calls haben, kann der Continuation-Passing-Stil eine naheliegende Lösung sein, aber C ++ macht ihn unlösbar.
Parallelität
Ein weiteres offensichtliches Beispiel ist die gleichzeitige Programmierung. Obwohl dies in C ++ durchaus möglich ist, ist es im Vergleich zu anderen Tools äußerst fehleranfällig, insbesondere bei der Kommunikation sequenzieller Prozesse in Sprachen wie Erlang, Scala und F #.
quelle
Dies ist eine alte Frage, aber da sie noch niemand erwähnt hat, füge ich eine Liste hinzu (und schreibe sie jetzt vor). Es ist einfach, einen Einzeiler in Haskell oder Python zu schreiben, der das Fizz-Buzz-Problem löst. Versuchen Sie das mal in C ++.
Während C ++ mit C ++ 11 massive Fortschritte in Richtung Modernität gemacht hat, ist es ein bisschen schwierig, es als "moderne" Sprache zu bezeichnen. C ++ 17 (das noch nicht veröffentlicht wurde) macht noch weitere Schritte, um den modernen Standards zu entsprechen, solange "modern" "nicht aus dem vorigen Jahrtausend" bedeutet.
Selbst das einfachste Verständnis, das man in Python in einer Zeile schreiben kann (und das sich an Guidos Zeilenlängenbeschränkung von 79 Zeichen hält), wird bei der Übersetzung in C ++ zu einer Vielzahl von Codezeilen, und einige dieser C ++ - Codezeilen sind ziemlich verworren.
quelle
Eine kompilierte Bibliothek, die einen Rückruf aufruft. Hierbei handelt es sich um eine benutzerdefinierte Elementfunktion einer benutzerdefinierten Klasse.
Dies ist in Objective-C möglich und macht die Programmierung der Benutzeroberfläche zum Kinderspiel. Sie können einer Schaltfläche sagen: "Bitte rufen Sie diese Methode für dieses Objekt auf, wenn Sie gedrückt werden", und die Schaltfläche wird dies tun. Es steht Ihnen frei, einen beliebigen Methodennamen für den gewünschten Rückruf zu verwenden. Er ist nicht im Bibliothekscode eingefroren. Sie müssen weder einen Adapter erben, damit er funktioniert, noch möchte der Compiler den Aufruf zur Kompilierungszeit auflösen. Ebenso wichtig ist, dass Sie zwei Schaltflächen anweisen können, zwei verschiedene Methoden desselben Objekts aufzurufen.
Ich habe noch keine ähnlich flexible Möglichkeit gesehen, einen Rückruf in einer anderen Sprache zu definieren (obwohl ich sehr interessiert wäre, davon zu hören!). Das nächste Äquivalent in C ++ ist wahrscheinlich die Übergabe einer Lambda-Funktion, die den erforderlichen Aufruf ausführt, wodurch der Bibliothekscode erneut als Vorlage eingeschränkt wird.
Es ist diese Eigenschaft von Objective-C, die mich gelehrt hat, die Fähigkeit einer Sprache zu bewerten, alle Arten von Objekten / Funktionen / was-auch-wichtig-Konzept-die-Sprache-enthält-frei herumzugeben, zusammen mit der Fähigkeit , sie zu speichern Variablen. Jeder Punkt in einer Sprache, der irgendeine Art von Begriff definiert, aber nicht die Möglichkeit bietet, ihn (oder einen Verweis darauf) in allen verfügbaren Arten von Variablen zu speichern, ist ein erheblicher Stolperstein und wahrscheinlich eine Quelle für viel Hässliches. doppelter Code. Leider weisen barocke Programmiersprachen in der Regel eine Reihe von Punkten auf:
In C ++ können Sie weder den Typ einer VLA notieren noch einen Zeiger darauf speichern. Dies verhindert effektiv echte mehrdimensionale Arrays mit dynamischer Größe (die in C seit C99 verfügbar sind).
In C ++ können Sie den Lambda-Typ nicht aufschreiben. Sie können es nicht einmal tippen. Daher gibt es keine Möglichkeit, ein Lambda zu umgehen oder einen Verweis darauf in einem Objekt zu speichern. Lambda-Funktionen können nur an Templates übergeben werden.
In Fortran können Sie den Typ einer Namensliste nicht notieren. Es gibt einfach keine Möglichkeit, eine Namensliste an irgendeine Routine weiterzugeben. Wenn Sie also einen komplexen Algorithmus haben, der zwei verschiedene Namenslisten verarbeiten kann, haben Sie Pech. Sie können den Algorithmus nicht einfach einmal schreiben und ihm die entsprechenden Namenslisten übergeben.
Dies sind nur ein paar Beispiele, aber Sie sehen den gemeinsamen Punkt: Wenn Sie zum ersten Mal eine solche Einschränkung sehen, ist es Ihnen normalerweise egal, weil es eine so verrückte Idee zu sein scheint, das Verbotene zu tun. Wenn Sie jedoch ernsthaft in dieser Sprache programmieren, kommen Sie schließlich an den Punkt, an dem diese genaue Einschränkung zu einem echten Ärgernis wird.
quelle
I have not seen a similarly flexible way to define a callback in any other language yet (though I'd be very interested to hear about them!)
Was Sie gerade beschreiben, klingt genauso wie der ereignisgesteuerte UI-Code in Delphi. (Und in .NET WinForms, das stark von Delphi beeinflusst wurde.)std::vector
. Obwohl es ein wenig weniger effizient ist, weil die Stapelzuordnung nicht verwendet wird, ist es für eine VLA funktional isomorph, so dass es nicht wirklich als ein "blub" -Typ-Problem gilt: C ++ - Programmierer können sich ansehen, wie es funktioniert, und einfach sagen: "ah yes , C macht das effizienter als C ++ ".std::function
."object::method
und es wird in eine Instanz konvertiert von welcher Schnittstelle auch immer der empfangende Code erwartet. C # hat Delegierte. Jede objektfunktionale Sprache hat dieses Merkmal, weil es im Grunde der Schnittpunkt der beiden Paradigmen ist.