Les opérateurs sont-ils plus lisibles que les mots-clés ou les fonctions? [fermé]

14

C'est un peu subjectif, mais j'espère avoir une meilleure compréhension des facteurs qui rendent un opérateur clair à utiliser vs obtus et difficile. J'ai récemment envisagé des conceptions de langage, et un problème sur lequel je reviens toujours est de savoir quand faire une opération clé dans la langue en tant qu'opérateur et quand utiliser un mot clé ou une fonction.

Haskell est quelque peu connu pour cela, car les opérateurs personnalisés sont faciles à créer et souvent un nouveau type de données est fourni avec plusieurs opérateurs pour être utilisé dessus. La bibliothèque Parsec, par exemple, est livrée avec une tonne d'opérateurs pour combiner les analyseurs, avec des gemmes comme >.et .> je ne me souviens même pas de ce qu'ils signifient en ce moment, mais je me souviens qu'ils étaient très faciles à travailler une fois que j'avais mémorisé ce ils signifient réellement. Un appel de fonction tel leftCompose(parser1, parser2)qu'aurait été mieux? Certainement plus verbeux, mais plus clair à certains égards.

Les surcharges d'opérateurs dans les langages de type C sont un problème similaire, mais confondu avec le problème supplémentaire de surcharger la signification des opérateurs familiers comme +avec de nouvelles significations inhabituelles.

Dans toute nouvelle langue, cela semble être un problème assez difficile. En F #, par exemple, la conversion utilise un opérateur de conversion de type dérivé mathématiquement, au lieu de la syntaxe de conversion de style C # ou du style VB détaillé. C #: (int32) xVB: CType(x, int32)F #:x :> int32

En théorie, un nouveau langage pourrait avoir des opérateurs pour la plupart des fonctionnalités intégrées. Au lieu de defou decou varpour la déclaration de variable, pourquoi pas ! nameou @ nameou quelque chose de similaire. Cela raccourcit certainement la déclaration suivie de la liaison: @x := 5au lieu de declare x = 5ou la let x = 5 plupart du code va nécessiter beaucoup de définitions de variables, alors pourquoi pas?

Quand un opérateur est-il clair et utile, et quand est-il obscurcissant?

CodexArcanum
la source
1
Je souhaite qu'un des 1000 développeurs qui se sont plaints auprès de moi du manque de surcharge d'opérateur de Java réponde à cette question.
smp7d
1
J'avais pensé à APL quand j'ai fini de taper la question, mais d'une certaine manière, cela clarifie le point et le déplace dans un territoire un peu moins subjectif. Je pense que la plupart des gens pourraient convenir qu'APL perd quelque chose dans la facilité d'utilisation grâce à son nombre extrême d'opérateurs, mais le nombre de personnes qui se plaignent quand une langue n'a pas de surcharge d'opérateur parle sûrement en faveur de certaines fonctions bien adaptées à agir comme opérateurs. Entre les opérateurs personnalisés interdits et rien que les opérateurs, il doit y avoir quelques directives pour une mise en œuvre et une utilisation pratiques.
CodexArcanum
Selon moi, le manque de surcharge d'opérateur est répréhensible car il privilégie les types natifs par rapport aux types définis par l'utilisateur (notez que Java le fait également d'autres manières). Je suis beaucoup plus ambivalent au sujet des opérateurs personnalisés de style Haskell, qui semblent être une invitation ouverte aux problèmes ...
comingstorm
2
@comingstorm: En fait, je pense que la méthode Haskell est meilleure. Lorsque vous disposez d'un ensemble fini d'opérateurs que vous pouvez surcharger de différentes manières, vous êtes souvent obligé de réutiliser des opérateurs dans différents contextes (par exemple +pour la concaténation de chaînes ou <<pour les flux). Avec Haskell, en revanche, un opérateur est soit juste une fonction avec un nom personnalisé (c'est-à-dire non surchargé) ou une partie d'une classe de type, ce qui signifie que bien qu'il soit polymorphe, il fait la même chose logique pour chaque type, et a même le même type de signature. Il en >>va de même >>pour chaque type et ne sera jamais un peu décalé.
Tikhon Jelvis

Réponses:

16

du point de vue de la conception d'un langage général, il n'y a pas de différence entre les fonctions et les opérateurs. On pourrait décrire les fonctions comme des opérations de préfixe avec n'importe quelle (ou même variable) arité. Et les mots-clés peuvent être vus comme de simples fonctions ou opérateurs avec des noms réservés (ce qui est utile pour concevoir une grammaire).

Toutes ces décisions finissent par se résumer à comment voulez-vous que la notation soit lue et que, comme vous le dites, soit subjective, bien que l'on puisse faire des rationalisations évidentes, par exemple, utiliser les opérateurs d'infixe habituels pour les mathématiques comme tout le monde les connaît

Enfin, Dijkstra a écrit une justification intéressante des notations mathématiques qu'il a utilisées, qui comprend une bonne discussion des compromis entre les notations infixe et préfixe

jk.
la source
4
J'adorerais vous donner un deuxième +1 pour le lien vers le papier Dijkstra.
Programmeur
Excellent lien, vous deviez publier un compte PayPal! ;)
Adriano Repetti
8

Pour moi, un opérateur cesse d'être utile lorsque vous ne pouvez plus lire la ligne de code à haute voix ou dans votre tête, et que cela ait du sens.

Par exemple, declare x = 5lit comme "declare x equals 5"ou let x = 5peut être lu comme "let x equal 5", ce qui est très compréhensible lorsqu'il est lu à haute voix, mais la lecture @x := 5est lue comme "at x colon equals 5"(ou "at x is defined to be 5"si vous êtes un mathématicien), ce qui n'a aucun sens.

Donc, à mon avis, utilisez un opérateur si le code peut être lu à haute voix et compris par une personne raisonnablement intelligente qui ne connaît pas la langue, mais utilisez des noms de fonction sinon.

Rachel
la source
5
Je ne pense pas que ce @x := 5soit difficile à comprendre - je le lis toujours à haute voix set x to be equal to 5.
FrustratedWithFormsDesigner
3
@Rachel, dans ce cas, :=a une utilisation plus large en notation mathématique en dehors des langages de programmation. De plus, des déclarations comme x = x + 1deviennent un peu absurdes.
ccoakley
3
Ironiquement, j'avais oublié que certaines personnes ne reconnaîtraient pas :=comme affectation. En fait, quelques langues l'utilisent. Smalltalk, je pense, étant peut-être le plus connu. Que l'affectation et l'égalité soient des opérateurs distincts est une toute autre boîte de vers, l'un étant probablement déjà couvert par une autre question.
CodexArcanum
1
@ccoakley Merci, je ne savais pas que :=c'était utilisé en mathématiques. La seule définition que j'ai trouvée était "est définie comme étant" et @x := 5serait donc traduite en anglais "at x is defined to be 5", ce qui pour moi n'a toujours pas autant de sens qu'il le devrait.
Rachel
1
Certainement Pascal a utilisé: = comme affectation, faute d'une flèche gauche en ASCII.
Vatine
4

Un opérateur est clair et utile lorsqu'il est familier. Et cela signifie probablement que la surcharge des opérateurs ne devrait être effectuée que lorsque l'opérateur choisi est suffisamment proche (comme dans la forme, la priorité et l'associativité) comme pratique déjà établie.

Mais en creusant un peu plus, il y a deux aspects.

La syntaxe (infixe pour l'opérateur par opposition au préfixe pour la syntaxe d'appel de fonction) et la dénomination.

Pour la syntaxe, il existe un autre cas où l'infixe est suffisamment clair que le préfixe pour justifier l'effort de se familiariser: lorsque les appels sont enchaînés et imbriqués.

Comparez a * b + c * d + e * favec +(+(*(a, b), *(c, d)), *(e, f))ou + + * a b * c d * e f(les deux sont analysables dans le même état que la version infixée). Le fait que +séparer les termes au lieu de précéder l'un d'eux de loin le rende plus lisible à mes yeux (mais vous devez vous rappeler les règles de priorité qui ne sont pas nécessaires pour la syntaxe des préfixes). Si vous avez besoin de combiner les choses de cette façon, le moyen d'avantages à long terme vaut le coût de l'apprentissage. Et vous conservez cet avantage même si vous nommez vos opérateurs avec des lettres au lieu d'utiliser des symboles.

Concernant la dénomination des opérateurs, je vois peu d'avantages à utiliser autre chose que des symboles établis ou un nom clair. Oui, ils peuvent être plus courts, mais ils sont vraiment énigmatiques et seront rapidement oubliés si vous n'avez pas de bonnes raisons de les connaître déjà.

Un troisième aspect si vous prenez un PDV de conception de langage, c'est si l'ensemble des opérateurs doit être ouvert ou fermé - pouvez-vous ajouter un opérateur ou non -, et si la priorité et l'associativité doivent être spécifiables par l'utilisateur ou non. Ma première prise serait d'être prudent et de ne pas fournir de priorité et d'associativité spécifiables par l'utilisateur, alors que je serais plus ouvert à avoir un ensemble ouvert (cela augmenterait la pression d'utiliser la surcharge de l'opérateur, mais diminuerait celle d'utiliser une mauvaise) juste pour bénéficier de la syntaxe infixe là où elle est utile).

AProgrammer
la source
Si les utilisateurs peuvent ajouter des opérateurs, pourquoi ne pas les laisser définir la priorité et l'associativité des opérateurs qu'ils ont ajoutés?
Tikhon Jelvis
@TikhonJelvis, C'était surtout de la prudence, mais il y a certains aspects à considérer si vous voulez pouvoir l'utiliser pour autre chose que des exemples. Par exemple, vous souhaitez lier la priorité et l'associativité à l'opérateur, et non à un membre donné de l'ensemble surchargé (vous avez choisi le membre une fois l'analyse terminée, le choix ne peut pas influencer l'analyse). Ainsi pour intégrer des bibliothèques utilisant le même opérateur, elles doivent s'accorder sur sa priorité et son associativité. Avant d'ajouter la possibilité, j'essaierais de comprendre pourquoi si peu de langues suivaient Algol 68 (AFAIK aucune) et permettaient de les définir.
Programmeur
Eh bien, Haskell vous permet de définir des opérateurs arbitraires et de définir leur priorité et leur associativité. La différence est que vous ne pouvez pas surcharger les opérateurs en soi - ils se comportent comme des fonctions normales. Cela signifie que vous ne pouvez pas avoir différentes bibliothèques essayant d'utiliser le même opérateur pour différentes choses. Puisque vous pouvez définir votre propre opérateur, il y a beaucoup moins de raisons de réutiliser les mêmes pour différentes choses.
Tikhon Jelvis
1

Les opérateurs en tant que symboles sont utiles lorsqu'ils ont un sens intuitif. +et -sont évidemment l'addition et la soustraction, par exemple, c'est pourquoi presque toutes les langues les utilisent comme opérateurs. Même avec la comparaison ( <et >sont enseignées à l' école primaire, et <=et >=sont des extensions intuitives des comparaisons de base lorsque vous comprenez qu'il n'y a pas de clés de comparaison sur le clavier souligné standard.)

*et /sont moins immédiatement évidents, mais ils sont utilisés universellement et leur position sur le pavé numérique juste à côté des touches +et -permet de fournir un contexte. Les C <<et >>sont dans la même catégorie: quand vous comprenez ce qu'est un décalage à gauche et un décalage à droite, il n'est pas trop difficile de comprendre les mnémoniques derrière les flèches.

Ensuite, vous arrivez à certains des plus étranges, des choses qui peuvent transformer le code C en soupe d'opérateur illisible. (Choisir C ici parce que c'est un exemple très lourd d'opérateur dont la syntaxe est à peu près connue de tous, en travaillant avec C ou avec des langages descendants.) Par exemple, alors %opérateur. Tout le monde sait ce que c'est: c'est un signe de pourcentage ... non? Sauf en C, cela n'a rien à voir avec la division par 100; c'est l'opérateur de module. Cela n'a aucun sens mnémoniquement, mais ça y est!

Pire encore, les opérateurs booléens. &comme un et a du sens, sauf qu'il y a deux &opérateurs différents qui font deux choses différentes, et les deux sont syntaxiquement valides dans tous les cas où l'un d'eux est valide, même si un seul d'entre eux a réellement un sens dans un cas donné. C'est juste une recette pour la confusion! Les opérateurs or , xor et not sont encore pires, car ils n'utilisent pas de symboles utiles sur le plan mnémotechnique et ne sont pas cohérents non plus. (Pas d' opérateur double- xor , et le logique et le bit à bit n'utilisent pas deux symboles différents au lieu d'un symbole et d'une version doublée.)

Et juste pour aggraver les choses, les opérateurs &et *sont réutilisés pour des choses complètement différentes, ce qui rend le langage plus difficile à analyser pour les personnes et les compilateurs. (Qu'est-ce que cela A * Bsignifie? Cela dépend entièrement du contexte: est-ce Aun type ou une variable?)

Certes, je n'ai jamais parlé avec Dennis Ritchie et je lui ai posé des questions sur les décisions de conception qu'il a prises dans la langue, mais il semble que la philosophie derrière les opérateurs était "des symboles pour le plaisir de symboles".

Comparez cela à Pascal, qui a les mêmes opérateurs que C, mais une philosophie différente pour les représenter: les opérateurs doivent être intuitifs et faciles à lire. Les opérateurs arithmétiques sont les mêmes. L'opérateur de module est le mot mod, car il n'y a pas de symbole sur le clavier qui signifie évidemment "module". Les opérateurs logiques sont and, or, xoret not, et il n'y a qu'un seul de chacun; le compilateur sait si vous avez besoin de la version booléenne ou au niveau du bit selon que vous travaillez sur des booléens ou des nombres, éliminant toute une classe d'erreurs. Cela rend le code Pascal beaucoup plus facile à comprendre que le code C, en particulier dans un éditeur moderne avec une coloration syntaxique afin que les opérateurs et les mots clés soient visuellement distincts des identifiants.

Mason Wheeler
la source
4
Pascal ne représente pas du tout une philosophie différente. Si vous lisez les papiers de Wirth, il a utilisé des symboles pour des choses comme andet ortout le temps. Cependant, lorsqu'il a développé Pascal, il l'a fait sur un ordinateur central Control Data, qui utilisait des caractères 6 bits. Qui a forcé la décision à utiliser des mots pour beaucoup de choses, pour la simple raison que le jeu de caractères n'a tout simplement pas avoir presque autant d' espace pour les symboles et tels que quelque chose comme ASCII. J'ai utilisé à la fois C et Pascal, et je trouve C beaucoup plus lisible. Vous pouvez préférer Pascal, mais c'est une question de goût, pas de fait.
Jerry Coffin
Pour ajouter à la remarque de @JerryCoffin sur Wirth, ses langages ultérieurs (Modula, Oberon) utilisent plus de symboles.
Programmeur
@AProgrammer: Et ils ne sont jamais allés nulle part. ;)
Mason Wheeler
Je pense |(et, par extension, ||) pour ou est logique. Vous le voyez également tout le temps pour l'alternance dans d'autres contextes, comme les grammaires, et il semble que cela divise deux options.
Tikhon Jelvis
1
Un avantage des mots-clés basés sur des lettres est que leur espace est beaucoup plus grand que les symboles. Par exemple, un langage de style Pascal peut inclure des opérateurs à la fois pour le "module" et le "reste", et pour la division entre les nombres entiers et à virgule flottante (qui ont des significations différentes au-delà des types de leurs opérandes).
supercat
1

Je pense que les opérateurs sont beaucoup plus lisibles lorsque leur fonction reflète fidèlement leur objectif mathématique familier. Par exemple, les opérateurs standard tels que +, -, etc. sont plus lisibles que Ajouter ou Soustraire lors de l'addition et de la soustraction. Le comportement de l'opérateur doit être clairement défini. Je peux tolérer une surcharge de la signification de + pour la concaténation de liste, par exemple. Idéalement, l'opération n'aurait pas d'effets secondaires, renvoyant une valeur au lieu de muter.

J'ai cependant eu du mal avec les opérateurs de raccourci pour des fonctions comme fold. De toute évidence, plus d'expérience avec eux faciliterait cela, mais je trouve foldRightplus lisible que /:.

Matt H
la source
4
Peut-être que la fréquence d'utilisation entre également en ligne de compte. Je ne vous penserais pas foldassez souvent pour qu'un opérateur économise beaucoup de temps. Cela pourrait également être la raison pour laquelle le LISPy (+ 1 2 3) semble étrange mais (ajouter 1 2 3) l'est moins. Nous avons tendance à considérer les opérateurs comme strictement binaires. Les >>.opérateurs de style de Parsec peuvent être loufoques, mais au moins la principale chose que vous faites dans Parsec est de combiner des analyseurs, donc les opérateurs pour cela sont très utilisés.
CodexArcanum
1

Le problème avec les opérateurs est qu'il n'y en a qu'un petit nombre par rapport au nombre de noms de méthode sensés (!) Qui pourraient être utilisés à la place. Un effet secondaire est que les opérateurs définissables par l'utilisateur ont tendance à introduire beaucoup de surcharge.

Certes, la ligne C = A * x + b * cest plus facile à écrire et à lire que C = A.multiplyVector(x).addVector(b.multiplyScalar(c)).

Ou bien, il est certainement plus facile d'écrire, si vous avez dans votre tête toutes les versions surchargées de tous ces opérateurs. Et vous pouvez le lire par la suite, tout en corrigeant l'avant-dernier bogue.

Maintenant, pour la plupart du code qui va être disponible, ça va. «Le projet passe tous les tests et il fonctionne» - pour beaucoup, un logiciel est tout ce que nous pourrions souhaiter.

Mais les choses fonctionnent différemment lorsque vous utilisez un logiciel qui va dans des domaines critiques. Trucs critiques pour la sécurité. Sécurité. Logiciel qui maintient les avions en l'air ou qui fait fonctionner les installations nucléaires. Ou un logiciel qui crypte vos e-mails hautement confidentiels.

Pour les experts en sécurité spécialisés dans l'audit de code, cela peut être un cauchemar. Le mélange d'une abondance d'opérateurs définis par l'utilisateur et d'une surcharge d'opérateur peut les laisser dans la situation désagréable qu'ils ont du mal à déterminer quel code va éventuellement fonctionner.

Ainsi, comme indiqué dans la question d'origine, il s'agit d'un sujet hautement subjectif. Et tandis que de nombreuses suggestions sur la façon dont les opérateurs doivent être utilisés peuvent sembler parfaitement sensées, la combinaison de quelques-uns seulement peut créer beaucoup de problèmes à long terme.

doppelfish
la source
0

Je suppose que l'exemple le plus extrême est APL, le programme suivant est le "jeu de la vie":

life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

Et si vous pouvez comprendre ce que cela signifie sans passer quelques jours avec le manuel de référence, alors bonne chance à vous!

Le problème est que vous avez un ensemble de symboles que presque tout le monde comprend intuitivement: +,-,*,/,%,=,==,&

et les autres qui nécessitent une explication avant de pouvoir être compris, et sont généralement spécifiques à la langue particulière. Par exemple, il n'y a pas de symbole évident pour spécifier quel membre d'un tableau vous voulez, [], () et même "." ont été utilisés, mais il n'y a pas non plus de mot clé évident que vous pourriez facilement utiliser, il y a donc lieu de faire un usage judicieux des opérateurs. Mais pas trop.

James Anderson
la source