Avantages d'une syntaxe de langue de gauche à droite

18

J'ai regardé une interview avec Herb Sutter sur Channel9 et il a mentionné à la fin de la vidéo que la syntaxe du langage de gauche à droite serait en haut de sa liste de souhaits pour une future norme C ++ (bien qu'il reconnaisse que la modification de C ++ de cette manière ferait à peu près une bête complètement différente).

Excepté:

  • plus compréhensible par l'homme, plus clair à l'œil nu, par ex.

    //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
  • plus facile à analyser (conduit à une meilleure prise en charge des outils comme mentionné dans la vidéo - par exemple, refactorisation de code)

quels sont les autres avantages d'une syntaxe "de gauche à droite" dans un langage de programmation. Je ne connais que Pascal et Go utilisant ce type de syntaxe (et Go ne va même pas jusqu'au bout comme je le comprends dans ce billet de blog dont j'ai également pris les exemples) Serait-il possible d'avoir un langage de programmation système avec ce type de la syntaxe?

celavek
la source
1
haskell utilise de gauche à droite:f :: (Int -> Int -> Int) -> Int -> Int
Karoly Horvath
1
ActionScript ainsi: function strlen(s:String):int {...}. Aussi, calcul lambda typé (d'où Haskell).
2011
2
Quelqu'un pourrait-il expliquer s'il vous plaît les votes de clôture :)? Je ne vois aucune raison de le fermer, mais je pose peut-être une "mauvaise" question.
1
Je n'ai pas voté pour fermer, mais, le commentaire de @Devjosh est pertinent, il est plus approprié pour les programmeurs, j'espère que quelqu'un le fera migrer ....
Nim
3
@Frank: et n'oubliez pas, en cas de pointeurs de fonctions, que la syntaxe est maladroite car le type réel est fractionné ! C'est un coup bas ...
Matthieu M.

Réponses:

12

L'avantage de base est que l'analyse est plus simple et unique. Notez qu'après l'analyse de la ligne, le compilateur saura quel est le type exact, donc à partir de là, la façon dont le type a été défini n'a plus d'importance.

Toute fonction qui renvoie un argument de type tableau ou un type de pointeur de fonction est difficile à lire actuellement:

// 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] &;

Et il y aurait moins de chance de malentendu (en tant qu'analyse la plus vexante ):

// 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{} };

Utiliser une approche similaire pour l'initialisation uniforme en C ++ 0x (c'est- {}à- dire pour identifier l'initialisation). Notez qu'avec une approche de gauche à droite, il est beaucoup plus clair ce que nous définissons. Beaucoup de gens (moi à coup sûr) ont été mordus à tout moment par cette erreur d'analyse dans le passé (plus d'une fois), et ce ne serait pas le cas avec une syntaxe de gauche à droite.

David Rodríguez - dribeas
la source
Et les expressions? Comment cette "syntaxe de gauche à droite" affecterait-elle la priorité de l'opérateur et l'ordre d'évaluation?
1
@celavek: Si vous revenez à l'interview, vous remarquerez qu'il ne veut pas changer toute la syntaxe du langage, seulement la déclaration s et la définition s. Bien sûr, cela pourrait pénétrer dans d'autres expressions, je ne suis pas trop sûr que la dernière ligne dans les exemples ci-dessus soit correcte de gauche à droite (pensez à la façon dont le temporaire est créé, cela pourrait devoir être changé ... en C # qui est résolu en donnant deux sémantiques à l' newopérateur pour structor class, ce qui n'est pas applicable au C ++ car en C ++ il n'y a pas de distinctions sur les types valeur / référence
David Rodríguez - dribeas
5

Comment nous sommes arrivés ici

La syntaxe C pour déclarer des points de fonction était destinée à refléter l'utilisation. Considérez une déclaration de fonction régulière comme ceci à partir de <math.h>:

double round(double number);

Pour avoir une variable ponctuelle, vous pouvez l'affecter à la sécurité de type en utilisant

fp = round;

vous devez avoir déclaré cette fpvariable de point de cette façon:

double (*fp)(double number);

Donc , tout ce que vous devez faire est de regarder la façon dont vous utilisez la fonction, et remplacer le nom de cette fonction avec une référence de pointeur, ce qui rounden *fp. Cependant, vous avez besoin d'un ensemble supplémentaire de parens, ce qui, selon certains, le rend un peu plus compliqué.

On peut dire que cela était plus facile dans le C d'origine, qui n'avait même pas de signature de fonction, mais n'y retournons pas, d'accord?

L'endroit où cela devient particulièrement désagréable est de savoir comment déclarer une fonction qui prend comme argument ou renvoie un pointeur sur une fonction, ou les deux.

Si vous aviez une fonction:

void myhandler(int signo);

vous pouvez le passer à la fonction signal (3) de cette façon:

signal(SIGHUP, myhandler);

ou si vous souhaitez conserver l'ancien gestionnaire,

old_handler = signal(SIGHUP, new_handler);

ce qui est assez facile. Ce qui est assez facile - ni joli, ni facile - c'est de bien faire les déclarations.

signal(int signo, ???)

Eh bien, revenez simplement à votre déclaration de fonction et échangez le nom pour une référence de point:

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

Parce que vous ne déclarez pas gotsig, vous pourriez trouver plus facile à lire si vous omettez:

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

Ou peut être pas. :(

Sauf que ce n'est pas suffisant, car signal (3) renvoie également l'ancien gestionnaire, comme dans:

old_handler = signal(SIGHUP, new_handler);

Alors maintenant, vous devez comprendre comment déclarer tout cela.

void (*old_handler)(int gotsig);

est suffisant pour la variable à laquelle vous allez attribuer. Notez que vous ne déclarez pas vraiment gotsigici, seulement old_handler. C'est vraiment suffisant:

void (*old_handler)(int);

Cela nous amène à une définition correcte du signal (3):

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

Typedefs à la rescousse

À ce moment-là, je pense que tout le monde conviendra que c'est un gâchis. Parfois, il vaut mieux nommer vos abstractions; souvent, vraiment. Avec le droit typedef, cela devient beaucoup plus facile à comprendre:

typedef void (*sig_t) (int);

Maintenant, votre propre variable de gestionnaire devient

sig_t old_handler, new_handler;

et votre déclaration pour le signal (3) devient juste

sig_t signal(int signo, sig_t handler);

ce qui est soudainement compréhensible. Se débarrasser des * supprime également certaines parenthèses déroutantes (et ils disent que les parens rendent toujours les choses plus faciles à comprendre - hah!). Votre utilisation est toujours la même:

old_handler = signal(SIGHUP, new_handler);

mais maintenant vous avez la possibilité de comprendre les déclarations pour old_handler, new_handleret même signallorsque vous les rencontrez pour la première fois ou devez les écrire.

Conclusion

Il se trouve que très peu de programmeurs C sont capables de concevoir par eux-mêmes les déclarations correctes pour ces choses sans consulter les documents de référence.

Je sais, parce que nous avions une fois cette question sur nos questions d'entrevue pour les personnes qui travaillent sur le noyau et les pilotes de périphérique. :) Bien sûr, nous avons perdu beaucoup de candidats de cette façon car ils se sont écrasés et ont brûlé sur le tableau blanc. Mais nous avons également évité d'embaucher des personnes qui prétendaient avoir une expérience antérieure dans ce domaine mais qui ne pouvaient pas réellement faire le travail.

En raison de cette difficulté généralisée, cependant, il est probablement non seulement raisonnable mais aussi raisonnable d'avoir un moyen de traiter toutes ces déclarations qui ne nécessitent plus que vous soyez un programmeur geek triple-alpha assis trois sigmas au-dessus de la moyenne juste pour utiliser cela. sorte de chose confortablement.

tchrist
la source
1
Conçu, difficile à suivre ... +1 pour l'effort, car cela illustre le fait qu'il est parfois difficile de bien faire les
choses
4

Je pense que vous avez un peu manqué le point lorsque vous vous êtes concentré sur le bit de gauche à droite.

Le problème du C et du C ++ est leur horrible grammaire, difficile à lire (humains) et à analyser (outils).

Une grammaire plus cohérente (ou régulière ) facilite les deux. Et une analyse plus facile signifie un outillage plus facile: la plupart des outils actuels n'ont pas le bon C ++, pas même le dernier plugin d'Eclipse car ils ont cherché à réinventer la roue ... et ont échoué, et ils ont probablement plus de personnes que le projet de système d'exploitation moyen.

Donc, vous l'avez probablement résolu en vous concentrant sur la lecture et l'analyse ... et c'est un gros problème :)

Matthieu M.
la source
C'est une grande surprise pour Eclipse toujours incapable d'analyser des choses comme les déclarations ci-dessus. Pourquoi n'utilisent-ils pas un véritable analyseur C comme celui de gcc?
tchrist
@tchrist: J'ai détecté deux problèmes avec Eclipse, et les deux semblent liés aux macros. Peut-être plus un problème de préprocesseur que la génération AST ainsi.
Matthieu M.