OCaml-Kritik: Gilt das noch?

14

Ich bin ein absoluter Neuling bei OCaml. Ich bin kürzlich auf diese Seite gestoßen, die eine Menge Kritik gegenüber OCaml auflistet.

Zu sehen, dass die Seite schon ziemlich alt ist (2007): Welche der dort aufgelisteten Aufzählungspunkte sind heute noch wahr? Zum Beispiel: Stimmt es immer noch, dass es unmöglich ist, ein generisches Objekt zu drucken?

Ich möchte klarstellen, dass ich keine Diskussion der darin geäußerten Meinungen suche. Ich frage, ob die aufgelisteten Informationen, z. B. die Tatsache, dass Ganzzahlen ohne Warnungen überlaufen, für neuere Versionen von OCaml noch korrekt sind

Andrea
quelle
5
Ich stimme dafür,
Philipp
3
Es gibt eine Abzweigung, an der diese Probleme
Den
7
Natürlich ist die Meinung in dem von Ihnen verlinkten Aufsatz gültig, aber ob diese Kritik für Sie relevant ist, ist eine ganz andere Frage. Beachten Sie, dass der Autor von einem Common Lisp-Hintergrund stammt und daher Lispish-Werte hat. Wenn Sie OCaml auf eine andere Weise als mit Lisp vergleichen (z. B. "OCaml ist Haskell für Sterbliche" oder "Wenn C eine funktionale Sprache wäre, hätten Sie OCaml"), werden Sie es weitaus befriedigender finden. Trotz all seiner Mängel ist OCaml eine großartige Sprache und ich möchte Sie ermutigen, sich trotzdem damit zu beschäftigen.
amon
5
Soweit ich weiß, gibt es keine Möglichkeit, einen Ganzzahlüberlauf in der Hardware zu erkennen. Sie müssen daher überall Laufzeitprüfungen einfügen, um ihn zu erkennen. aber der Autor entlassen mit "Bignum" auf der Grundlage der Leistung! Die Beschwerde über die statische Schreibweise besagt, dass Sicherheitsgurte schlecht sind, weil Sie vielleicht denken, dass Sie bei einem Autounfall nicht sterben können. Die Beschwerde über die Unveränderlichkeit von Modulen besagt, dass er Dinge monkeypatchen möchte - eine anti-modulare und fehleranfällige Praxis. Der "kleine Typ Zoo" hat nichts mit Typinferenz zu tun. Es ist klar, wo seine Vorurteile liegen .
Doval
2
@Doval: Natürlich können Sie einen Überlauf in der Hardware erkennen. Der Umgang damit ist jedoch eine Frage der Software.
Mason Wheeler

Antworten:

13

Dieser Artikel wird an mehreren Stellen besprochen:

Zusammenfassend: Ja, OCaml ist kein Lisp und nein, es ist nicht perfekt (was bedeutet das?). Ich denke nicht, dass die Punkte, die in diesem Blogbeitrag erwähnt werden, für alltägliche O'Caml-Programmierer relevant sind.

Nach dem Studium von O'Caml halte ich es für eine interessante Sprache, die Ihnen beim Erstellen von Programmen helfen kann, die Sie nicht einmal wagen würden, beispielsweise in C / C ++ / Java zu schreiben: Schauen Sie sich zum Beispiel Frama-C an .

Um eine aktuelle Beschreibung von O'Caml zu erhalten, empfehlen wir Ihnen, sich mit seinen Funktionen vertraut zu machen: Die Sprache unterstützt starke Techniken zur statischen Typprüfung, mit denen sich Implementierungen auf die Erstellung performanter und dennoch sicherer Laufzeiten konzentrieren können.

Wichtig : Ich bin kein OCaml-Experte: Wenn Sie einer von ihnen sind und sehen, dass ich etwas fürchterlich Falsches geschrieben habe, korrigieren Sie mich bitte. Ich werde diesen Beitrag entsprechend bearbeiten.

Statische Typprüfung

  • Falsches Gefühl der Sicherheit

    Das ist wahr, aber offensichtlich.

    Durch die statische Eingabe erhalten Sie Beweise, denen Sie in Bezug auf eine Teilmenge der Eigenschaften Ihres Programms vertrauen können. Wenn Sie nicht akzeptieren, dass Sie alle formellen Anforderungen erfüllen, kann ein durchschnittliches Programm (das kein Spielzeug ist) Programmierfehler aufweisen, die nur zur Laufzeit auftreten können.

    Dann können dynamische Prüftechniken angewendet werden: Der OCaml-Compiler verfügt über Flags zum Generieren von ausführbaren Dateien mit Debuginformationen usw. Oder er kann Code generieren, der dem Programmierer blind vertraut und Typinformationen so weit wie möglich löscht. Programmierer, die robuste Programme wünschen, sollten dynamische Prüfungen explizit implementieren.

    Das Gleiche gilt zB für Common Lisp, aber umgekehrt: Dynamische Typen zuerst, mit optionalen Typdeklarationen und Compiler-Direktiven an zweiter Stelle.

  • Einige Grundtypen

    Es gilt weiterhin: Die Hauptsprache hat sich nicht (oder nicht dramatisch) geändert.

  • Silent Integer Overflow

    Dies ist die Norm in den meisten Sprachen, in denen ein Ganzzahlüberlauf von Hand überprüft wird. Ich kenne keine Bibliothek, die Operationen überprüft, um festzustellen, ob ein Überlauf auftreten kann.

  • Modul Unveränderlichkeit

    1. Der Autor erwähnt Functors, aber ich verstehe nicht, wie sein Beispiel nicht umgesetzt werden kann. Beim Lesen des Kapitels First Class Modules von https://realworldocaml.org scheint es, dass Module verwendet werden können , um neue Module zu komponieren und zu erstellen . Natürlich erfordert das Ändern eines vorhandenen Moduls eine Änderung des Quellcodes, aber auch dies ist unter Programmiersprachen nicht ungewöhnlich.

    2. " Funktionen werden semantisch INLINE übersetzt"

    Der obige reddit-Thread ist anderer Meinung und besagt, dass die Bindung zum Zeitpunkt des Links aufgelöst wird. Dies ist jedoch ein Implementierungsdetail, und ich denke, dass sich das Hervorheben semantisch auf die Art und Weise bezieht, wie Funktionen aufgelöst werden. Beispiel:

     let f x y = x + y ;;
     let g a b = f b a ;;
     let f x y = x * y ;;
     exit (g 2 3) ;;
    

    Das obige Programm kompiliert und gibt bei Ausführung 5 zurück, da ges mit der ersten Version von definiert ist f, als ob die aufrufende Funktion gden Aufruf von inliniert hätte f. Dies ist übrigens nicht "schlecht", sondern entspricht lediglich O'Camls Namensschattierungsregeln.

    Zusammenfassend : Ja, Module sind unveränderlich . Sie sind aber auch zusammensetzbar .

  • Polymorphismus verursacht Laufzeitfehler

    Ich kann den genannten Fehler nicht reproduzieren. Ich vermute, dass es sich um einen Compilerfehler handelt.

Keine Makros

In der Tat gibt es keine Makros, sondern Präprozessoren (OcamlP4, OcamlP5, ...).

Geringfügige Sprachlosigkeit

  • Rekord Feldnamen Hölle

    Richtig, aber Sie sollten Module verwenden:

    1. Zwei Felder von zwei Datensätzen haben in OCaml dieselbe Bezeichnung
    2. Auflösen von Feldnamen
  • Syntax

    Gilt immer noch (aber das ist wirklich nur Syntax).

  • Kein Polymorphismus

    Gilt immer noch, aber irgendwie gibt es Leute, die das anstelle von Lisps numerischem Turm bevorzugen (ich weiß nicht warum). Ich nehme an, es hilft bei der Typinferenz.

  • Inkonsistente Funktionssätze

    Siehe das OCaml Batteries Included- Projekt. Insbesondere BatArray als Beispiel map2für Arrays.

  • Keine dynamischen Variablen

    Kann implementiert werden:

    1. http://okmij.org/ftp/ML/dynvar.txt
    2. http://okmij.org/ftp/ML/index.html#dynvar
  • Optionale ~ Argumente saugen

    Aufgrund von Spracheinschränkungen können Sie in Common Lisp keine optionalen Argumente und Schlüsselwortargumente mischen. Heißt das, es ist scheiße? (Natürlich kann dies mit Makros geändert werden (siehe zB meine Antwort )). Siehe O'Caml Dokumentation für optionale und benannte Argumente in O'Caml.

  • Teilweise Inkonsistenz der Argumentanwendung

    Ich denke nicht, dass das in der Praxis wirklich ärgerlich ist.

  • Lesbarkeit der Arithmetik

    Es gilt, aber Sie können R oder Python für numerische Probleme verwenden, wenn Sie dies vorziehen.

  • Stille Namenskonfliktlösung

    Gilt immer noch, aber beachten Sie, dass dies gut dokumentiert ist.

  • Keine Objekteingabe / -ausgabe

    Gilt immer noch.

Implementierung, Bibliotheken

Diese ändern sich jeden Tag: Es gibt keine endgültige Antwort.

Schließlich,

"Sie sollten OCaml (oder noch besser Haskell) ausprobieren, auch wenn Sie der Meinung sind, dass es scheiße ist und Sie nicht vorhaben, es zu verwenden. Ohne es ist Ihre Informatikausbildung unvollständig, genauso wie es ohne etwas Lisp und C (oder) unvollständig ist noch besser, Versammlung). "

... gilt immer noch.

Core-Dump
quelle
Vielen Dank, ich werde mir die Links ansehen, aber ich frage nicht wirklich, ob diese Meinungen berechtigt sind. Ich frage, ob die Fakten noch stimmen. Zum Beispiel: Gibt es eine Möglichkeit, einen algebraischen Datentyp auszudrucken? Ist es immer noch richtig, dass ganze Zahlen ohne Warnungen überlaufen? Gibt es heute eine Möglichkeit, Dateien zu bearbeiten und zu entsorgen, ohne jedes Mal die Kesselplatte schreiben zu müssen, um das Schließen von Dateien bei Fehlern zu behandeln?
Andrea
@ Andrea Ich habe meine Antwort bearbeitet.
Coredump
1
"... es gibt Leute, die das anstelle von Lisps numerischem Turm bevorzugen (ich weiß nicht warum). Ich nehme an, es hilft bei der Typinferenz." Bingo. Das Typensystem von SML und OCaml erfordert, dass jeder Ausdruck nur einen Typ hat. Das Überladen von mathematischen Operatoren durch SML ist eine in die Sprache integrierte Ausnahme. Dies gilt auch für Haskell; Diese Art der Überladung zu ermöglichen, war die Motivation hinter den Typklassen. Der Haken ist, dass Sie nur eine Typklasseninstanz pro Typ haben können. Sie können eine Ganzzahl auch nicht blind in einen Gleitkommawert gleicher Größe konvertieren - ein 64-Bit-Gleitkommawert hat nur eine Genauigkeit von 54 Bit.
Doval
@Doval Wie wäre es mit einer Eingabe des numerischen Turms für Racket? Können wir uns eine OCaml-Bibliothek vorstellen, die Typklassen oder Generalized Algebraic Data Types (GADT) ausnutzt, um polymorphe mathematische Operatoren bereitzustellen? Zur Konvertierung: Nicht alle Operationen sind möglich, aber einige sind und könnten getippt werden.
Coredump
2
@Doval: "Re: polymorphe mathematische Operatoren, ich denke, es gibt keine Möglichkeit, ohne die Typklassen von Haskell zu implementieren." F # hat polymorphe mathematische Operatoren ohne Typklassen.
Jon Harrop
7

Zu sehen, dass die Seite schon ziemlich alt ist (2007): Welche der dort aufgelisteten Aufzählungspunkte sind heute noch wahr?

  • Falsches Gefühl der Sicherheit . Das ist Unsinn.

  • Einige Grundtypen . OCaml verfügt nun über Bytes und Bytearrays, jedoch keine integrierten Unicode-Zeichenfolgen, 16-Bit-Ganzzahlen, vorzeichenlosen Ganzzahlen, 32-Bit-Gleitkommazahlen, Vektoren oder Matrizen. Bibliotheken von Drittanbietern bieten einige davon an.

  • Silent Integer Overflow . Unverändert, aber es war nie ein Problem.

  • Modul Unveränderlichkeit . Seine Empfehlung, dass Funktionen und Module wandelbar sein sollten, ist ein grimmiger Rückfall für Lisp und eine wirklich schlechte Idee. Sie können Module mit ersetzen, includewenn Sie möchten, aber Sie können sie natürlich nicht mutieren.

  • Polymorphismus verursacht Laufzeitfehler . Dies ist ein großes Problem mit OCaml und es wurde nicht behoben. Während sich Ihre Typen zu einer polymorphen Gleichheit entwickeln, werden Vergleich und Hashing fehlschlagen, wenn sie auf Typen wie Funktionen stoßen und das Problem nur sehr schwer behoben werden kann. F # hat eine großartige Lösung für dieses Problem.

  • Keine Makros . Ironischerweise hatte OCaml, als er dies schrieb, tatsächlich die volle Unterstützung für Makros, aber sie haben sich jetzt entschieden, die Funktion herauszunehmen.

  • Wrapper . Dies war ein echtes Problem und es wurde nicht behoben. Es gibt noch kein try ... finallyKonstrukt in der OCaml-Sprache und keinen Wrapper, der es in der stdlib implementiert.

  • Orte . Unverändert, aber kein Problem.

  • Rekord Feldnamen Hölle . Strukturieren Sie Ihren Code richtig mit Modulen.

  • Syntax . Unverändert, aber kein Problem.

  • Kein Polymorphismus . Dies war größtenteils Unsinn, als er es schrieb und nichts hat sich geändert.

  • Inkonsistente Funktionssätze . OCaml hat noch keine consFunktion. Das ist gut. Ich möchte kein Lisp-Zeug in meiner Sprache, danke.

  • Keine dynamischen Variablen . War eine gute Sache über OCaml. Ist immer noch eine gute Sache über OCaml.

  • Optionale ~ Argumente saugen . Optionale Argumente rocken. Ich habe Microsoft verärgert, dass sie optionale Argumente zu F # hinzufügen sollen.

  • Teilweise Inkonsistenz der Argumentanwendung . Wie?

  • Lesbarkeit der Arithmetik . Dies hat sich geändert, seit ich OCaml ~ vor 8 Jahren nicht mehr benutze. Anscheinend kannst du es jetzt tun Int64.((q * n - s * s) / (n - 1L)).

  • Stille Namenskonfliktlösung . Er hat versucht, in der REPL eine vollständige Softwareentwicklung durchzuführen, wie Sie es in Lisp tun würden. Mach das nicht in OCaml. Verwenden Sie Dateien und Stapelkompilierungen, die auf die REPL zurückgreifen, nur zum Testen, Ausführen von Einwegcode und für interaktives technisches Rechnen.

  • Reihenfolge der Bewertung . Das war falsch, als er es schrieb. Die Reihenfolge der Auswertung ist in OCaml undefiniert.

  • Keine Objekteingabe / -ausgabe . Er zitierte eine Drittanbieter-Bibliothek, die dieses "Problem" bereits löste.

  • Der Compiler stoppt nach dem ersten Fehler . Wie?

  • Kein Stack-Trace für nativ kompilierte ausführbare Dateien . Fest.

  • Debugger saugt . Ich habe den Debugger nie benutzt. Die statische Typprüfung fängt fast alle meine Fehler ab.

  • GC saugt . Ich fand OCamls GC hervorragend, mit Ausnahme eines Hauptproblems: Die globale Sperre verhindert die parallele Programmierung.

  • Keine impliziten Vorwärtserklärungen . Die gegenseitige Rekursion ist in allen MLs ausdrücklich vorgesehen. Die einzige Kuriosität ist, dass typeDefinitionen standardmäßig rekursiv sind, während letBindungen standardmäßig nicht rekursiv sind.

  • Funktionsrunde fehlt . OCaml hat immer noch eine reine Standardbibliothek, aber Bibliotheken von Drittanbietern wie Jane St's Core roundund Freunde.

  • Listen . List.mapist immer noch nicht rekursiv. Ich reichte Patches ein, um solche schwerwiegenden Fehler zu beheben, und musste Jahre warten, bis sie in Veröffentlichungen auftauchten. Listen sind natürlich noch unveränderlich. Und so sollten sie sein.

  • Geschwindigkeit . Ich glaube, die Kompilierungszeiten für große polymorphe Varianten wurden behoben.

  • Mustervergleich . Ein Triumph der Hoffnung über die Realität. Die Lisp-Community hat dies versäumt. Daher meine 10. Regel: Jedes ausreichend komplizierte Lisp-Programm enthält eine ad-hoc, informell spezifizierte und fehlerbehaftete Implementierung der Hälfte von OCamls Pattern Match Compiler.

Zum Beispiel: Stimmt es immer noch, dass es unmöglich ist, ein generisches Objekt zu drucken?

Als er schrieb, dass man es nicht einfach machen kann:

print value

Sie können den hübschen Drucker jedoch von der obersten Ebene aus als Bibliotheksaufruf aufrufen und ihm die erforderlichen Typinformationen geben. Und es gab ein Makro, mit dem Sie Datenstrukturen mit Anmerkungen versehen können, damit hübsche Drucker automatisch generiert werden.

Jon Harrop
quelle
Pattern Matching: OCaml Source Code und Optima beziehen sich beide auf dasselbe Paper: "Optimizing Pattern Matching". Ich würde sagen, dass hier weder "ad-hoc", "bug-ridden" noch "informell spezifiziert" realistisch anwendbar sind. "F # hat eine großartige Lösung für dieses Problem": Ich würde gerne ein bisschen mehr Details dazu sehen, wenn möglich. Die Antwort ist gut, aber fluchend, wenn es sich um conseinen schlechten Ton handelt.
Coredump
2
@coredump: "Ich würde gerne ein bisschen mehr Details dazu sehen, wenn möglich." F # hat einen benutzerdefinierten Ad-hoc-Polymorphismus für bestimmte Operatoren und Funktionen. Sie können also +Ints, Floats und Komplexe verwenden, aber Sie können auch Ihre eigenen Typen definieren und eine Überladung hinzufügen +, um an Ihrem Typ zu arbeiten. Dies bietet der Kürze und Lesbarkeit von Lisp oder Haskell die vorhersehbar gute Leistung von SML oder OCaml und erreicht etwas, was keine andere Sprache kann.
Jon Harrop
@coredump: "OCaml-Quellcode und optima beziehen sich beide auf dasselbe Dokument". Die in diesem Artikel beschriebenen Techniken bauen vollständig auf dem ML-Typ-System auf. Ein Typsystem, das Lisp nicht hat. Optima kann und kann nicht viel von dem tun, was in diesem Artikel beschrieben wird. Schauen Sie sich dazu Abschnitt 4.2 "Vollständigkeitsinformationen verwenden" an. In Lisp gibt es keine Informationen zur Vollständigkeit, da es keine Variantentypen gibt. Zum Beispiel wählt OCaml basierend auf der Anzahl der Blätter zwischen verschachtelten Sprüngen oder einer Versandtabelle, aber diese Informationen sind in Lisp unbekannt.
Jon Harrop
Lisp und OCaml sind auf verschiedene Dinge ausgelegt (z. B. Dynamik, bildbasierte Programmierung). Aber Lisp hat eine Art System, unterscheidet sich von Hindley-Milner, und Implementierungen haben ausbeuten es während der Kompilierung. Sehen Sie sich diese SBCL-Sitzung mit Beispielen aus dem Abschnitt 4.2 des Dokuments an. Die Vollständigkeit wird vom Compiler bereits anhand von Typinferenzen und Deklarationen überprüft. Optima könnte implementierungsspezifischen Code für die Makroerweiterung zur Kompilierungszeit (oder SBCL-VOPs) hinzufügen, um über andere Strategien zu verfügen, aber es gibt nicht genügend Anreize, dies zu tun.
Coredump
Wie gilt das für benutzerdefinierte algebraische Datentypen?
Jon Harrop