Das "Blub-Paradoxon" und C ++


37

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ß.



2
Genau. Solange dies nicht zu einer C ++ - gegen-Lisp-Debatte wird, gibt es meiner Meinung nach hier etwas zu lernen.
Jeffythedragonslayer

@ MasonWheeler: 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 .
Robert Harvey

2
@RobertHarvey: "Alle Sprachen sind im Sinne eines Turing-Äquivalents gleich mächtig, aber das ist nicht der Sinn des Wortes, um das sich Programmierer kümmern. (Niemand möchte eine Turing-Maschine programmieren.) Die Art von Power-Programmierern mag das nicht sein formal definierbar, aber eine Möglichkeit zu erklären wäre zu sagen, dass es sich um Funktionen handelt, die man nur in der weniger mächtigen Sprache erhalten kann, wenn man einen Dolmetscher für die mächtigere Sprache schreibt. " - Paul Graham in einer Fußnote zum betreffenden Trollpost. (Sehen Sie, was ich meine?)
Mason Wheeler

@ Mason Wheeler: (Nicht wirklich.)
Robert Harvey

Antworten:


16

Nun, seit du Haskell erwähnt hast:

  1. 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.

    map :: (a -> b) -> [a] -> [b]
    map f [] = []
    map f (x:xs) = f x : map f xs
  2. 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.

  3. Funktionsprogrammierung allgemein. Verwenden von Karten und Falten anstelle von Iterationen. Rekursion. Es geht darum, auf einer höheren Ebene zu denken.

  4. Faule Bewertung. Auch hier geht es darum, auf einer höheren Ebene zu denken und das System die Evaluierung durchführen zu lassen.

  5. 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.


2
Ein Hinweis zum Pattern Matching: Ich würde nicht sagen, dass es im Allgemeinen einfacher ist zu schreiben, aber nachdem Sie ein wenig über das Ausdrucksproblem gelesen haben, wird klar, dass Dinge wie if- und switch-Anweisungen, Aufzählungen und das Observer-Pattern allesamt minderwertige Implementierungen algebraischer Datentypen sind + Mustervergleich. (Und lassen Sie uns nicht einmal anfangen, wie Vielleicht macht Nullzeiger Ausnahmen obsolet)
hugomg

Was Sie sagen, ist wahr, aber das Ausdrucksproblem betrifft Einschränkungen algebraischer Datentypen (und die doppelten Einschränkungen von Standard-OOP).
Blaisorblade

@hugomg Meinten Sie Besuchermuster statt Beobachter?
Sebastian Redl

Ja. Ich
tausche

@hugomg Es geht nicht darum Maybe(für C ++ siehe std::optional), Dinge explizit als optional / nullable / maybe zu markieren.
Deduplizierer

7

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 :

auto memoize(alias Fn, T...)(T args) {
    auto key = tuple(args);                               //Key is all the args
    static typeof(Fn(args))[typeof(key)] cache;           //Hashtable!
    return key in cache ? cache[key] : (cache[key] = Fn(args));
}

Alles, was Sie tun müssen, um es anzurufen, ist so etwas wie:

int fib(int n) { return n > 1 ? memoize!(fib)(n - 1) + memoize!(fib)(n - 2) : 1;}
fib(60);

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):

(define (memoize f)
    (let ((table (list)))
        (lambda args
            (cdr
                (or (assoc args table)
                    (let ((entry (cons args (apply f args))))
                        (set! table (cons entry table))
                        entry))))))
(define (fib n)
        (if (<= n 1)
            1
            (+ (fib (1- n))
                (fib (- n 2)))))))
(set! fib (memoize fib))

1
Sie lieben APL, wo Sie alles in einer Zeile schreiben können? Größe spielt keine Rolle!
Bo Persson

@ Bo: Ich habe APL nicht verwendet. Ich bin mir nicht sicher, was Sie unter "Größe spielt keine Rolle" verstehen, aber stimmt mit meinem Code etwas nicht, was Sie dazu bringt, das zu sagen? Und gibt es einen Vorteil, wenn Sie dies in einer anderen Sprache (wie C ++) tun, die mir nicht bekannt ist? (Ich habe die Variablennamen ein wenig bearbeitet, für den Fall, dass Sie sich darauf bezogen haben.)
Mehrdad,

1
@Mehrdad - Mein Kommentar ist über das kompakteste Programm kein Zeichen der besten Programmiersprache. In diesem Fall würde APL zweifellos gewinnen, da Sie die meisten Dinge mit einem einzelnen Zeichenoperator erledigen. Das einzige Problem ist, dass es nicht lesbar ist.
Bo Persson

@ Bo: Wie gesagt, ich habe APL nicht empfohlen. Ich habe es noch nie gesehen. Die Größe war ein Kriterium (obwohl ein wichtiges, wie Sie sehen können, wenn Sie dies mit C ++ versuchen) ... aber stimmt etwas mit diesem Code nicht?
Mehrdad

1
@Matt: Ihr Code hat eine Funktion gespeichert , aber dieser Code kann jede Funktion speichern . Diese sind überhaupt nicht wirklich gleichwertig. Wenn Sie tatsächlich versuchen, eine Funktion höherer Ordnung wie diese in C ++ 0x zu schreiben, ist dies sehr viel mühsamer als in D (obwohl dies durchaus möglich ist, obwohl dies in C ++ 03 nicht möglich ist).
Mehrdad

5

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 yieldin 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.


Ich kann mir gar nicht vorstellen, wie schwierig es wäre, in C ++ und C ++ 2003 Generatoren zu erstellen. C ++ 2011 würde es einfacher machen, aber immer noch nicht trivial. Generatoren, die routinemäßig mit C ++, C # und Python arbeiten, sind mit Leichtigkeit die Funktion, die ich in C ++ am meisten vermisse (jetzt, da C ++ 2011 Lambdas hinzugefügt hat).

Ich weiß, dass ich dafür erschossen werde, aber wenn ich Generatoren unbedingt in C ++ implementieren müsste, müsste ich ... setjmpund ... verwenden longjmp. 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.
Mike DeSimone

@Mike DeSimone, können Sie kurz erläutern, wie Sie eine Lösung mit setjmp und longjmp versuchen würden?

1
Coroutinen sind für Funktoren isomorph.
GManNickG

1
@Xeo, @Mike: xkcd.com/292
Mehrdad

5

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.


Coroutinen gibt es in FreeRTOS ( siehe hier ), das in C implementiert ist. Ich frage mich, wie man sie in C ++ zum Laufen bringt .
Mike DeSimone

Co-Routinen sind ein unangenehmer Hack, um Objekte in C zu emulieren. In C ++ werden Objekte zum Bündeln von Code und Daten verwendet. Aber in C kannst du nicht. Sie verwenden also den Co-Routine-Stack zum Speichern der Daten und die Co-Routine-Funktion zum Speichern des Codes.
MSalters


1
@Ferruccio: Danke für den Link ... es gibt auch ein paar in dem Wikipedia-Artikel. @MSalters: Was bringt dich dazu, Co-Routinen als "einen bösen Hack" zu bezeichnen? Scheint mir eine sehr willkürliche Perspektive zu sein. Verwenden eines Stacks zum Speichern des Status, der auch von rekursiven Algorithmen ausgeführt wird - sind sie auch hackisch? FWIW, Coroutines und OOP kamen ungefähr zur selben Zeit (Anfang der 1960er Jahre) auf die Bühne ... zu sagen, dass Ersteres ein Hack für Objekte in C ist, erscheint bizarr ... Ich könnte mir vorstellen, dass wenige C-Programmierer damals daran interessiert waren, Objekte zu emulieren, > 15 Jahre vor C ++.
Tony

4

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.


Was war der Unterschied in der Geschwindigkeit?
quant_dev

1
@quant_dev: natürlich ein Vielfaches von einer Milliarde!
Matt Ellen

Ich habe es nie wirklich gemessen, aber ich hatte das Gefühl, dass die großen O's anders waren. Ich habe die C ++ - Version ursprünglich in einem funktionalen Stil geschrieben und es gab auch Geschwindigkeitsprobleme, bis ich sie lehrte, die Datenstrukturen zu modifizieren, anstatt neue, geänderte zu erstellen. Aber das machte es auch schwieriger, den Code zu lesen ...
jeffythedragonslayer

2

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 carund cdrnehmen 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?


Sie finden, dass die Wahl ein Doppelschwert ist. Wir alle suchen es, aber es hat eine dunkle Seite und es macht uns unglücklich. Es ist besser, eine sehr definierte Sicht auf die Welt und definierte Grenzen zu haben, in denen man agieren kann. Menschen sind sehr kreative Wesen und können mit begrenzten Werkzeugen großartige Dinge tun. Um es zusammenzufassen, ich sage, es ist nicht die Programmiersprache, sondern Talentleute, die jede Sprache zum Singen bringen können!
Tschad,

"Vorschlagen heißt erschaffen, definieren heißt zerstören." Zu glauben, Sie könnten das Leben mit einer anderen Sprache leichter machen, fühlt sich gut an, aber wenn Sie den Sprung geschafft haben, müssen Sie sich mit den Warzen der neuen Sprache auseinandersetzen.
Mike DeSimone

2
Ich würde sagen, dass Englisch viel mächtiger und ausdrucksvoller ist als jede andere Programmiersprache, und dennoch erweitern sich die Grenzen seines Nutzens von Tag zu Tag und sein Nutzen ist immens. Ein Teil der Kraft und Ausdruckskraft kommt von der Fähigkeit, auf der geeigneten Abstraktionsebene zu kommunizieren und neue Abstraktionsebenen zu erfinden, wenn sie verbunden sind.
Molbdnilo

@Mike dann musst du mit der Kommunikation mit der vorherigen Sprache innerhalb der neuen umgehen;)
Tschad

2

Assoziative Arrays

Eine typische Art der Datenverarbeitung ist:

  • die Eingabe lesen und daraus eine hierarchische Struktur aufbauen,
  • Erstellen von Indizes für diese Struktur (z. B. unterschiedliche Reihenfolge),
  • Extrakte (gefilterte Teile) davon erstellen,
  • einen Wert oder eine Wertegruppe (Knoten) finden,
  • die Struktur neu anordnen (Knoten löschen, hinzufügen, anfügen, Unterelemente auf der Grundlage einer Regel entfernen usw.),
  • Scannen Sie durch den Baum und drucken Sie einige Teile davon aus oder speichern Sie sie.

Das richtige Werkzeug dafür ist das assoziative Array .

  • Die beste Sprachunterstützung für assoziative Arrays, die ich gesehen habe, ist MUMPS . Assoziative Arrays sind: 1. Immer sortiert. 2. Sie können auf der Festplatte (sogenannte Datenbank) mit derselben Syntax erstellt werden. (Nebeneffekt: Es ist extrem leistungsfähig als Datenbank, der Programmierer hat Zugriff auf den nativen Btree. Bestes NoSQL-System aller Zeiten.)
  • Mein zweiter Preis geht an PHP , ich mag foreach und einfache Syntax, wie $ a [] = x oder $ a [x] [y] [z] ++ .

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.


1
Vollständige Offenlegung: MUMPS ist nicht für jeden etwas dabei. Zitat: Um ein etwas realeres Beispiel für den Horror zu geben, der MUMPS ist, nehmen Sie zunächst an einem internationalen verschleierten C-Code-Wettbewerb teil, einem Schuss Perl, zwei häufigen Maßnahmen von FORTRAN und SNOBOL und den unabhängigen und unkoordinierten Beiträgen von Dutzende medizinische Forscher, und los geht's.
Mike DeSimone

1
In Python können Sie entweder den integrierten dictTyp 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] = 8ist schwierig, da die Sprache in die Zukunft schauen muss, um zu sehen, ob Sie einen Wert festlegen oder eine andere Ebene erstellen werden. der Ausdruck a[x][y]an sich sagt es dir nicht.
Mike DeSimone

MUMPS ist ursprünglich eine Basic-ähnliche Sprache mit assoziativen Arrays (möglicherweise direkt auf der Festplatte gespeichert!). Spätere Versionen enthalten prozedurale Erweiterungen, die dem Kern-PHP sehr ähnlich sind. Eine Angst vor Basic und PHP sollte Mumps fürchterlich finden, andere nicht. Programmierer nicht. Und denken Sie daran, es ist ein sehr altes System, alle seltsamen Dinge, wie Anweisungen aus einem Buchstaben (obwohl Sie auch vollständige Namen verwenden können), die Reihenfolge der LR-Bewertung usw. - sowie nicht-seltsame Lösungen - haben nur ein Ziel: Optimierung .
ern0

"Wir sollten kleine Wirkungsgrade vergessen, sagen wir in 97% der Fälle: Vorzeitige Optimierung ist die Wurzel allen Übels. Dennoch sollten wir unsere Chancen in diesen kritischen 3% nicht verpassen." - Donald Knuth. Was Sie beschreiben, klingt für mich nach einer Legacy-Sprache, deren Schwerpunkt auf der Abwärtskompatibilität liegt, und das ist in Ordnung. Persönlich halte ich in diesen Anwendungen die Wartbarkeit für wichtiger als die Optimierung, und eine Sprache mit nicht-algebraischen Ausdrücken und Befehlen aus einem Buchstaben klingt kontraproduktiv. Ich bediene den Kunden, nicht die Sprache.
Mike DeSimone

@Mike: sehr unterhaltsame Links, die du gepostet hast, ich habe viel gelacht, als ich sie gelesen habe.
Shuttle87

2

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.


2

Gibt es ein mächtiges Sprachfeature oder eine mächtige Sprache, die Sie in einer Sprache verwenden, die schwer zu konzipieren oder zu implementieren wäre, wenn Sie nur in c ++ schreiben würden?

Gibt es nützliche Konzepte oder Techniken, die Sie in anderen Sprachen kennengelernt haben, die Sie sich nur schwer vorstellen können, wenn Sie in c ++ geschrieben oder "nachgedacht" hätten?

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 EVALzu 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 #.


1

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.


Beachten Sie auch: Der größte Teil meiner Programmierung ist in C ++. Ich mag die Sprache.
David Hammen

Ich dachte, der Ranges-Vorschlag sollte dieses Problem lösen? (Nicht einmal in C ++ 17 denke ich)
Martin Ba

2
"Massive Schritte in die Moderne": Welche "modernen" Funktionen bietet C ++ 11, die im aktuellen Jahrtausend erfunden wurden?
Giorgio

@MartinBa - Mein Verständnis des Vorschlags "Bereiche" ist, dass es einen Ersatz für Iteratoren darstellt, die einfacher zu bearbeiten und weniger fehleranfällig sind. Ich habe keine Vorschläge gesehen, die so interessant wären wie Listenverständnisse.
Jules

2
@Giorgio - Welche Funktionen einer derzeit beliebten Sprache wurden im laufenden Jahrtausend erfunden?
Jules

0

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.


1
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.)
Mason Wheeler

2
"In C ++ kann man den Typ einer VLA nicht aufschreiben" - [...] in C ++ sind VLAs im C99-Stil unnötig, weil wir haben 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 ++ ".
Jules

2
"In C ++ kann man den Typ eines Lambdas nicht aufschreiben. Man kann ihn nicht einmal tippen. Daher gibt es keine Möglichkeit, ein Lambda zu umgehen oder einen Verweis darauf in einem Objekt zu speichern std::function."
Jules

3
"Ich habe noch keine ähnlich flexible Möglichkeit gesehen, einen Rückruf in einer anderen Sprache zu definieren (obwohl ich sehr daran interessiert wäre, davon zu hören!)." - In Java können Sie schreiben object::methodund 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.
Jules

@Jules Ihre Argumente sind genau das, worum es im Blub-Paradox geht: Als erfahrener C ++ - Programmierer sehen Sie dies nicht als Einschränkungen. Dies sind jedoch Einschränkungen, und andere Sprachen wie C99 sind in diesen spezifischen Punkten leistungsfähiger. Zum letzten Punkt: Es gibt in vielen Sprachen mögliche Problemumgehungen, aber ich kenne keine, die es Ihnen wirklich ermöglicht, den Namen einer Methode an eine andere Klasse weiterzugeben und sie für ein Objekt aufrufen zu lassen, das Sie ebenfalls bereitstellen.
cmaster
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.