Vorteile einer Syntax von links nach rechts

18

Ich habe ein Interview mit Herb Sutter auf Channel9 gesehen und er erwähnte am Ende des Videos, dass die Syntax von links nach rechts auf seiner Wunschliste für einen zukünftigen C ++ - Standard ganz oben stehen würde (obwohl er anerkennt, dass man C ++ auf diese Weise modifiziert würde so ziemlich ein ganz anderes Biest ergeben).

Außer, abgesondert, ausgenommen:

  • für Menschen verständlicher, mit bloßem Auge klarer, z

    //C syntax
    
    /*pointer to function taking a pointer to function(which takes 2 integers as 
    
    arguments and returns an int), and an int as arguments and returning an int*/
    
    int (*fp)(int (*ff)(int x, int y), int b)
    
    //Go analogous syntax which is left to write
    
    f func(func(int,int) int, int) int
  • einfacher zu analysieren (führt zu einer besseren Werkzeugunterstützung, wie im Video erwähnt - z. B. Code-Refactoring)

Welche weiteren Vorteile bietet eine "von links nach rechts" -Syntax in einer Programmiersprache? Ich kenne nur Pascal und Go, die diese Art von Syntax verwenden (und Go geht nicht einmal den ganzen Weg, wie ich aus diesem Blog-Beitrag verstehe, aus dem ich auch die Beispiele entnommen habe). Wäre es machbar, eine Systemprogrammiersprache mit dieser Art zu haben der Syntax?

Celavek
quelle
1
haskell verwendet von links nach rechts:f :: (Int -> Int -> Int) -> Int -> Int
Karoly Horvath
1
Actionscript auch: function strlen(s:String):int {...}. Auch typisierte Lambda-Kalkül (daher Haskell).
outis
2
Kann mir jemand bitte die Schlussabstimmung erklären :)? Ich sehe keinen Grund, es zu schließen, aber vielleicht stelle ich eine "falsche" Frage.
1
Ich habe nicht für den Abschluss gestimmt, aber der Kommentar von @Devjosh ist zutreffend. Er ist besser für Programmierer geeignet. Hoffentlich wird ihn jemand migrieren ...
Nim
3
@Frank: und vergessen Sie nicht, dass bei Funktionszeigern die Syntax umständlich ist, weil der tatsächliche Typ aufgeteilt ist ! Das ist ein Tiefschlag ...
Matthieu M.

Antworten:

12

Der grundlegende Vorteil ist, dass das Parsen einfacher und einzigartiger ist. Beachten Sie, dass der Compiler nach dem Parsen der Zeile den genauen Typ kennt. Daher ist es von nun an unerheblich, wie der Typ definiert wurde.

Alle Funktionen, die ein Argument vom Typ Array oder Funktionszeiger zurückgeben, sind derzeit schwer zu lesen:

// common way of obtaining the static size in elements of an array:
template <typename T, int N>
char (&array_size_impl( T (&)[N] ))[N];
// alternative parser:
template <typename T, int N>               // this would probably be changed also
array_simple_impl function( T [N] & ) char [N] &;

Und es gibt weniger Chancen für Missverständnisse (als die ärgerlichste Analyse ):

// Current C++
type x( another_type() );      // create an instance x of type passing a
                               // default constructed another_type temporary?
                               // or declare a function x that returns type and takes as argument
                               // a function that has no arguments and returns another_type
// How the compiler reads it:
x function( function() another_type ) type;

// What many people mean:
x type { another_type{} };

Verwenden eines ähnlichen Ansatzes zur einheitlichen Initialisierung in C ++ 0x (dh {}zum Identifizieren der Initialisierung). Beachten Sie, dass bei einer Annäherung von links nach rechts viel klarer ist, was wir definieren. Viele Menschen (ich bin mir sicher) wurden in der Vergangenheit (mehr als einmal) von diesem Parsing-Fehler gebissen, und das wäre bei einer Syntax von links nach rechts nicht der Fall.

David Rodríguez - Dribeas
quelle
Wie wäre es mit Ausdrücken? Wie würde sich diese "von links nach rechts" -Syntax auf die Priorität und Reihenfolge der Auswertung des Operators auswirken?
1
@celavek: Wenn Sie zum Interview zurückkehren, werden Sie feststellen, dass er nicht die gesamte Syntax der Sprache ändern möchte, sondern nur die Deklarationen und Definitionen . Das könnte natürlich auch in anderen Ausdrücken vorkommen. Ich bin mir nicht sicher, ob die letzte Zeile in den obigen Beispielen von links nach rechts richtig ist (überlegen Sie, wie die temporäre Datei erstellt wird, die möglicherweise geändert werden muss ... in C # Dies wird gelöst, indem dem newOperator zwei Semantiken für structoder gegeben werden class, die für C ++ nicht zutreffen, da in C ++ keine Unterscheidung zwischen Wert- und Referenztypen besteht.
David Rodríguez - dribeas
5

Wie wir hierher gekommen sind

Die C-Syntax zum Deklarieren von Funktionspunkten sollte die Verwendung spiegeln. Betrachten Sie eine reguläre Funktionsdeklaration wie diese von <math.h>:

double round(double number);

Um eine Punktvariable zu haben, können Sie diese mit der Typensicherheit zuweisen

fp = round;

Sie müssten fpdiese Punktvariable folgendermaßen deklariert haben :

double (*fp)(double number);

Sie müssen sich also nur ansehen, wie Sie die Funktion verwenden würden, und den Namen dieser Funktion durch eine Zeigerreferenz ersetzen, die roundin *fp. Sie benötigen jedoch einen zusätzlichen Satz von Parens, was einige sagen würden, macht es ein bisschen chaotischer.

Im Original-C, das nicht einmal über eine Funktionssignatur verfügte, war dies früher einfacher, aber lasst uns nicht dorthin zurückkehren, ok?

Besonders unangenehm wird es, wenn man herausfindet, wie eine Funktion deklariert wird, die entweder ein Argument annimmt oder einen Zeiger auf eine Funktion zurückgibt, oder beides.

Wenn Sie eine Funktion hatten:

void myhandler(int signo);

Sie könnten es auf folgende Weise an die Signalfunktion (3) übergeben:

signal(SIGHUP, myhandler);

oder wenn Sie den alten Handler behalten möchten, dann

old_handler = signal(SIGHUP, new_handler);

Das ist ziemlich einfach. Was ziemlich einfach ist - weder hübsch noch einfach -, ist die Richtigkeit der Erklärungen.

signal(int signo, ???)

Sie kehren einfach zu Ihrer Funktionsdeklaration zurück und tauschen den Namen gegen eine Punktreferenz aus:

signal(int sendsig, void (*hisfunc)(int gotsig));

Da Sie nicht deklarieren, gotsigfällt es Ihnen möglicherweise leichter, zu lesen, wenn Sie Folgendes weglassen:

signal(int sendsig, void (*hisfunc)(int));

Oder vielleicht nicht. :(

Abgesehen davon, dass dies nicht gut genug ist, da das Signal (3) auch den alten Handler zurückgibt, wie in:

old_handler = signal(SIGHUP, new_handler);

Also müssen Sie jetzt herausfinden, wie Sie all diese deklarieren können.

void (*old_handler)(int gotsig);

ist genug für die Variable, der Sie zuweisen werden. Beachten Sie, dass Sie gotsighier nur nicht wirklich deklarieren old_handler. Das ist also wirklich genug:

void (*old_handler)(int);

Das bringt uns zu einer korrekten Definition für signal (3):

void (*signal(int signo, void (*handler)(int)))(int);

Typedefs zur Rettung

Ich denke, zu diesem Zeitpunkt sind sich alle einig, dass das ein Durcheinander ist. Manchmal ist es besser, Ihre Abstraktionen zu benennen. oft wirklich. Mit dem Recht typedefwird dies viel einfacher zu verstehen:

typedef void (*sig_t) (int);

Jetzt wird Ihre eigene Handlervariable

sig_t old_handler, new_handler;

und Ihre Deklaration für Signal (3) wird gerecht

sig_t signal(int signo, sig_t handler);

das ist plötzlich nachvollziehbar. Wenn Sie die * loswerden, werden Sie auch einige der verwirrenden Klammern los (und sie sagen, dass Parens die Dinge immer leichter verständlich machen - hah!). Ihre Nutzung ist immer noch die gleiche:

old_handler = signal(SIGHUP, new_handler);

aber jetzt haben Sie Chance, die Erklärungen zu verstehen , für old_handler, new_handlerund selbst signalwenn man sie zuerst oder Notwendigkeit begegnen , sie zu schreiben.

Fazit

Es stellt sich heraus, dass nur sehr wenige C-Programmierer in der Lage sind, die richtigen Deklarationen für diese Dinge selbst zu erstellen, ohne Referenzmaterialien zu konsultieren.

Ich weiß, weil wir diese Frage einmal in unseren Interviewfragen für Leute hatten, die Kernel- und Gerätetreiberarbeiten erledigen. :) Sicher, wir haben viele Kandidaten auf diese Weise verloren, als sie auf dem Whiteboard abgestürzt und verbrannt sind. Wir haben aber auch vermieden, Leute einzustellen, die behaupteten, sie hätten bereits Erfahrungen in diesem Bereich gesammelt, konnten diese Arbeit aber nicht ausführen.

Aufgrund dieser weit verbreiteten Schwierigkeit ist es jedoch wahrscheinlich nicht nur sinnvoll, sondern auch vernünftig, über all die Erklärungen zu verfügen, für die Sie nicht länger ein Triple-Alpha-Geek-Programmierer sein müssen, der drei Sigmas über dem Durchschnitt sitzt, nur um diesen zu verwenden so etwas bequem.

tchrist
quelle
1
Erfunden, schwer nachzuvollziehen ... +1 für die Mühe, da dies den Punkt verdeutlicht, dass es manchmal schwierig ist, dies in C.
Celavek,
4

Ich denke, Sie haben den Punkt etwas verpasst, an dem Sie sich auf das Links-Rechts-Stück konzentriert haben.

Das Problem von C und C ++ ist die horrende Grammatik, die schwer zu lesen (Menschen) und zu analysieren (Werkzeuge) ist.

Eine konsistentere (oder regelmäßigere ) Grammatik erleichtert beides. Und einfacheres Parsen bedeutet einfacheres Tooling: Die meisten aktuellen Tools stimmen nicht mit C ++ überein, auch nicht mit dem neuesten Eclipse-Plugin, da sie das Rad neu erfinden wollten ... und fehlgeschlagen sind. Wahrscheinlich haben sie mehr Leute als das durchschnittliche OS-Projekt.

Also hast du es wahrscheinlich geschafft, wenn du dich auf das Lesen und Parsen konzentrierst ... und das ist eine große Sache :)

Matthieu M.
quelle
Das ist eine große Überraschung, dass Eclipse immer noch Dinge wie die obigen Deklarationen nicht analysieren kann. Warum verwenden sie keinen echten C-Parser wie von gcc?
Tchrist
@tchrist: Ich habe zwei Probleme mit Eclipse festgestellt, und beide scheinen mit Makros verbunden zu sein. Vielleicht eher ein Präprozessorproblem als eine AST-Generation also.
Matthieu M.