Wie kann man die Orthogonalität einer Programmiersprache überprüfen / beweisen?

10

Ich kenne das Konzept der Orthogonalität, aber gibt es aus Sicht der Programmiersprache eine Möglichkeit, es zu verifizieren / zu beweisen?

Zum Beispiel kann man in C # publicoder staticfür eine Methodensignatur verwenden. Sie können eines oder beide verwenden und sie würden sich nicht gegenseitig stören, also sind sie orthogonal zueinander, oder?

Meine Frage ist, wie gehe ich mit den restlichen Funktionen um, insbesondere mit Funktionen, die nicht miteinander zusammenhängen?

Müssen alle Funktionen nebeneinander existieren / gestapelt werden?

Gibt es eine Programmiersprache, die zu 100% orthogonal ist?

Joan Venge
quelle
Beginnen Sie am (nahen) Anfang: Assemblersprache?
Matthew Flynn
1
Um etwas tatsächlich zu beweisen, benötigen Sie eine formale Definition dafür. Und wenn Ihre Definition so groß sein soll wie die C # -Spezifikation, wird es viel Arbeit kosten, etwas zu beweisen.
Svick

Antworten:

4

Ich bin nicht sicher, ob Orthogonalität bei Allzwecksprachen höherer Ordnung wie C # als nützliche oder gültige Metrik dienen kann, da sie die Unterscheidung von "Operationen" und "Operanden" erfordert - den kleinen Teilen der Sprache, die nicht einfach sind unterscheidbar in solchen übergeordneten Sprachen wie C #.

Mein Verständnis der Orthogonalität basiert auf der Assembler-Sprache, in der die Orthogonalität des Befehlssatzes einer bestimmten CPU oder eines bestimmten Mikrocontrollers angibt, ob abhängig von den Datentypen Einschränkungen für die von dieser CPU oder diesem Controller ausgeführten Operationen bestehen. In früheren Zeiten war dies wichtig, da nicht jede CPU Operationen mit Bruchzahlen oder Zahlen unterschiedlicher Länge usw. unterstützte.

In dieser Hinsicht würde ich lieber die Orthogonalität der Common Intermediate Language überprüfen, indem ich die Stack Machine-Sprache als Ziel für den C # -Compiler verwende, nicht C # selbst.

Wenn Sie wirklich an der Orthogonalität von C # interessiert sind und ich mich hier nicht irre (für welchen Zweck auch immer), würde ich vorschlagen, nach einigen genetischen Programmieralgorithmen zu suchen . Sie können diese verwenden, um aus den angegebenen Schlüsselwörtern (auch aus den bedeutungslosen) verschiedene Programme zu generieren, und Sie können einfach automatisch prüfen, ob diese kompilierbar sind. Auf diese Weise können Sie automatisch erkennen, welche Elemente der Sprache miteinander kombiniert werden können, und einige Aspekte Ihrer Orthogonalitätsmetrik ableiten.

Alexander Galkin
quelle
6

Der Begriff "Orthogonalität" ist ein Laienbegriff für einen präzisen mathematischen Begriff: Die Sprachbegriffe bilden eine anfängliche Algebra (siehe Wikipedia).

Es bedeutet im Grunde "es gibt eine 1-1 Entsprechung zwischen Syntax und Bedeutung". Das bedeutet: Es gibt genau eine Möglichkeit, Dinge auszudrücken, und wenn Sie einen Ausdruck an einer bestimmten Stelle platzieren können, können Sie auch einen anderen Ausdruck dort platzieren.

Eine andere Möglichkeit, über "orthogonal" nachzudenken, besteht darin, dass die Syntax dem Substitutionsprinzip folgt. Wenn Sie beispielsweise eine Anweisung mit einem Steckplatz für einen Ausdruck haben, kann jeder Ausdruck dort abgelegt werden, und das Ergebnis ist immer noch ein syntaktisch gültiges Programm. Darüber hinaus, wenn Sie ersetzen

Ich möchte betonen, dass "Bedeutung" kein Rechenergebnis impliziert. Es ist klar, dass 1 + 2 und 2 + 1 beide gleich 3 sind. Die Begriffe sind jedoch unterschiedlich und implizieren eine andere Berechnung, selbst wenn sie das gleiche Ergebnis haben. Die Bedeutung ist unterschiedlich, genauso wie zwei Sortieralgorithmen unterschiedlich sind.

Möglicherweise haben Sie von "Abstract Syntax Tree" (AST) gehört. Das Wort "abstrakt" bedeutet hier genau "orthogonal". Technisch gesehen sind die meisten ASTs nicht abstrakt!

Vielleicht haben Sie von der Programmiersprache "C" gehört? Die Notation vom Typ C ist nicht abstrakt. Erwägen:

int f(int);

Hier ist also eine Funktionsdeklaration, die den Typ zurückgibt int. Der Typ eines Zeigers auf diese Funktion ist gegeben durch:

int (*)(int)

Beachten Sie, dass Sie den Typ der Funktion nicht schreiben können! C Typ Notation saugt bigtime! Es ist nicht abstrakt. Es ist nicht orthogonal. Angenommen, wir möchten eine Funktion erstellen, die den obigen Typ anstelle von int akzeptiert:

int (*) ( int (*)(int) )

Alles in Ordnung .. aber .. was ist, wenn wir es stattdessen zurückgeben möchten:

int (*)(int) (*) (int)

Woops! Ungültig. Fügen wir Parens hinzu:

(int (*)(int)) (*) (int)

Woops! Das funktioniert auch nicht. Wir müssen das tun (es ist der einzige Weg!):

typedef int (intintfunc*) (int);
intintfunc (*)(int)

Jetzt ist es in Ordnung, aber hier ein typedef verwenden zu müssen, ist schlecht. C saugt. Es ist nicht abstrakt. Es ist nicht orthogonal. So machen Sie das in ML:

 int -> (int -> int)

Wir verurteilen C auf Syntaxebene.

Ok, jetzt lass uns C ++ auspeitschen. Wir können die Dummheit oben mit Vorlagen beheben und eine ML-ähnliche Notation erhalten (mehr oder weniger):

fun<int, int>
fun< fun<int,int>, int>

Das eigentliche Typensystem ist jedoch durch Referenzen grundlegend fehlerhaft: Wenn Tes sich um einen Typ handelt, handelt es sich dann um T&einen Typ? Die Antwort ist waffelig: Wenn Sie auf Syntaxebene einen Typ U = T & haben, ist U & zulässig, aber es bedeutet nur T &: Eine Referenz auf eine Referenz ist die ursprüngliche Referenz. Das ist scheiße! Es bricht die Eindeutigkeitsanforderung semantisch. Schlimmer noch: T & & ist syntaktisch nicht zulässig: Dies verstößt gegen das Substitutionsprinzip. Daher brechen C ++ - Referenzen die Orthogonalität je nach Bindungszeit (Parsing oder Typanalyse) auf zwei verschiedene Arten. Wenn Sie verstehen wollen, wie man das richtig macht, gibt es kein Problem mit Zeigern!

Fast keine echten Sprachen sind orthogonal. Sogar das Schema, das eine große Klarheit des Ausdrucks vorgibt, ist es nicht. Es kann jedoch beurteilt werden, dass viele gute Sprachen eine "ziemlich nahe an der orthogonalen Merkmalsbasis" haben, und dies ist eine gute Empfehlung für eine Sprache, die sowohl auf die Syntax als auch auf die zugrunde liegende Semantik angewendet wird.

Yttrill
quelle
Sie denken also, ML ist orthogonaler als andere? Was ist mit Lisp und Haskell?
Joan Venge
1
@joan: Nun, Lisp hat keine Funktionen, so dass es die Anforderungen in Vaccuuo erfüllt :)
Yttrill
@joan: Ich bin kein Haskell-Programmierer, daher ist es etwas schwer zu sagen, aber das Vorhandensein von "extrem hoher Funktionalität" in Haskell weist auf eine starke Orthogonalität hin: Eine kohärente Implementierung von Monaden oder Pfeilen ist nur möglich Der Rest der Sprache hat erhebliche "Orthogonalität"
Yttrill
Was denkst du über Pascal? Scheint viel besser als C.
Supercat
Ich weiß, dass mein Kommentar fast 4 Jahre zu spät ist, aber ich bin gerade darauf gestoßen. Diese Antwort ist in fast allem falsch. Sogar das ganze "es ist der einzige Weg!" Teil ist einfach falsch. Sie können dies ohne ein typedef-Beispiel leicht ausdrücken int (*intintfunc())(int) { ... }- intintfunc ist eine Funktion, die keine Argumente akzeptiert und einen Zeiger auf eine Funktion zurückgibt, die 1 int-Argument akzeptiert und einen int-Wert zurückgibt.
Wiz
4

Der Nachweis der Orthogonalität ist negativ. Es bedeutet, dass Sie keine Konstrukte haben, die nicht orthogonal sind, was bedeutet, dass es viel einfacher ist zu beweisen, dass etwas nicht orthogonal ist als es ist.

In der Praxis sprechen die meisten Menschen über die Orthogonalität von Programmiersprachen in Grad, anstatt entweder vollständig orthogonal zu sein oder nicht. Wenn das Wissen, etwas in einem Kontext zu tun, in einen anderen Kontext übersetzt wird und "das tut, was Sie erwarten", wird diese Sprache als orthogonaler bezeichnet. LISP wird als sehr orthogonal angesehen, da alles eine Liste ist, aber ich glaube nicht, dass es aufgrund einiger Redundanzen, die die Verwendung vereinfachen, zu 100% orthogonal ist. C ++ wird als nicht sehr orthogonal angesehen, da es viele kleine "Fallstricke" gibt, bei denen es nicht ganz so funktioniert, wie Sie es sich vorstellen.

Karl Bielefeldt
quelle
3

Achtung, ich weiß nichts über dieses Thema.

Ein kurzer Blick auf Wikipedia scheint darauf hinzudeuten, dass Orthogonalität hauptsächlich auf Entwurfsmuster und Systemdesign abzielt. In Bezug auf Programmiersprachen gibt der Eintrag an, dass Befehlssätze orthogonal sind, wenn es für jede mögliche Aktion einen und nur einen Befehl gibt oder vielmehr kein Befehl einen anderen überlappt.

Für C # würde ich mir vorstellen, dass es orthogonal ist, da die meisten Syntaxtricks ( foreachdie mir in den Sinn kommen) einfach Frontends für speziell geformte Versionen des Basiskonstrukts sind (werden foreachzu forSchleifen). Insgesamt unterstützt die Sprache nur wirklich, Dinge auf eine einzige Weise zu tun, obwohl syntaktischer Zucker zusätzliche Möglichkeiten bietet, sie zu tun. Und schließlich wird alles auf MSIL(oder wie auch immer es heutzutage heißt) kompiliert und MSIList wahrscheinlich orthogonal.

Wenn Sie den Vorbehalt machen, dass syntaktisches Zuckermaterial im Wesentlichen ein "Wrapper" ist, um es auf "harte Weise" zu tun, können Sie die verschiedenen Merkmale der Sprache analysieren, Zucker weglassen und feststellen, ob es Konstrukte gibt, die sich wirklich überschneiden. Wenn nicht, könnte ich mir vorstellen, dass Sie die Sprache orthogonal deklarieren könnten.

Meine zwei Cent.

digitlworld
quelle
Ich denke, wenn sowohl für als auch foreach Merkmale einer Sprache sind, während einer ein syntaktischer Zucker des anderen ist (wo die Auswirkungen von foreach mit for erzielt werden könnten), verliert die Sprache dort ihre Orthogonalität.
vpit3833
Kann nicht do...whileverwendet werden, um den gleichen Effekt wie zu erzielen for? Ich habe noch nie davon gehört, dass es sich um syntaktischen Zucker handelt.
Matthew Flynn
1
@ MatthewFlynn: Bah! Sie sind beide syntaktischen Zucker, Sie könnten einfach Ihre Iteration durch eine rekursive Funktion ersetzen! ;)
FrustratedWithFormsDesigner
2
@FrustratedWithFormsDesigner: Ist das nicht nur syntaktischer Zucker für GOTOs?
Ivan
2
@MatthewFlynn do whilegarantiert eine Ausführung einer einzelnen Schleife und überprüft den Zustand nachträglich . forprüft zuerst die Bedingung und garantiert keine einzige Ausführung.
digitlworld
2

Meine Frage ist, wie gehe ich mit den restlichen Funktionen um, insbesondere mit Funktionen, die nicht miteinander zusammenhängen?

Sie machen weiter, was Sie tun, und zählen alle Kombinationen auf, die funktionieren oder verboten sind.

Das ist alles. Es ist ziemlich schmerzhaft zu tun.

Müssen alle Funktionen nebeneinander existieren / gestapelt werden?

Wenn alle Funktionen in disjunkte Teilmengen unterteilt werden können, die sich nicht gegenseitig stören, sind alle sinnvoll.

Alle Datenstrukturen arbeiten mit allen primitiven Typen. Alle Ausdrucksoperatoren arbeiten mit allen Typen. Dies sind gebräuchliche Definitionen der Orthogonalität. Aber vielleicht möchten Sie mehr (oder weniger)

Manchmal gibt es jedoch Sonderfälle aufgrund von Betriebssystemen oder Legacy-Bibliotheken, die nicht orthogonal sind.

Außerdem sind einige Typen überhaupt nicht sehr anpassungsfähig. Mit Python können Sie beispielsweise zwei Wörterbuchobjekte für die "Bestellung" vergleichen. Aber es gibt fast keine vernünftige Definition von "Ordnen" unter Wörterbüchern. Python definiert eine, aber es ist ziemlich umstritten. Lässt dieser Sonderfall Wörterbücher einen Orthogonalitätstest nicht bestehen?

Wie orthogonal ist "orthogonal genug"? Was müssen Sie sehen, um mit dem Grad der Orthogonalität in Ihrer Sprache zufrieden zu sein ?

S.Lott
quelle
2

Die Liste der unorthogonalen Merkmale ist in den meisten Programmiersprachen in der Tat lang, z

  • Anonyme Klassen stehen im Konflikt mit der Java-Reflexion
  • Generischer und Typ-Löschkonflikt mit Java-Reflexion
  • Arrays unterscheiden sich aufgrund ihres speziellen Typs etwas von anderen Objekten, obwohl es sich um Objekte handelt.
  • statische und instanzielle Methoden sind nicht identisch, z. B. können Sie eine statische Methode nicht überschreiben
  • verschachtelte Klasse sind ein Nachdenken
  • Einfluss der dynamischen oder statischen Typisierung auf die Nachrichtenversandstrategie (siehe z. B. diesen Randfall in C #)
  • usw.

Das sind nur ein paar, die mir in den Sinn kommen, aber es gibt viele andere und auch in anderen Sprachen.

Es ist schwer sicherzustellen, dass es keine subtilen Interferenzen zwischen Sprachmerkmalen gibt. Wie CAR Hoare in seinem Artikel "Hinweise zum Design von Programmiersprachen" angibt:

Ein Teil des Sprachdesigns besteht aus Innovation. Diese Aktivität führt isoliert zu neuen Sprachfunktionen. Der schwierigste Teil des Sprachdesigns liegt in der Integration : Wählen Sie einen begrenzten Satz von Sprachfunktionen aus und polieren Sie sie, bis das Ergebnis ein konsistentes einfaches Framework ist, das keine rauen Kanten mehr aufweist.

Wahrscheinlich besteht ein guter Schritt zur Erhöhung der Orthogonalität darin, Konzepte zu vereinheitlichen (dies geht in Richtung der Antwort von @ karl-bielfeld). Wenn alles ist, sagen wir eine Liste oder ein Objekt, besteht die Möglichkeit, dass es weniger Konflikte gibt. Oder anstatt eine Klasse nachträglich verschachtelt zu haben, machen Sie sie zu einer Kernfunktion.

Die meisten Programmiersprachenpapiere belegen bestimmte Eigenschaften der Sprache (z. B. Typensicherheit) in einer Teilmenge (einem "Kern") der formalisierten Sprache. Hier sollten wir das Gegenteil tun und beweisen, dass alle Funktionen sicher zusammengesetzt sind. Das bedeutet auch, dass man definieren sollte, was es bedeutet, "zu komponieren". Bedeutet es "rennen"? (In diesem Fall ist der obige Link zum Randfall mit dynamischer und statischer Typisierung sicher). Bedeutet es, "sicher" zu sein? Bedeutet es, aus Entwicklersicht vorhersehbar zu sein?

Das alles ist sehr interessant - aber auch sehr herausfordernd.

ewernli
quelle