Pourquoi les opérateurs définis par l'utilisateur ne sont-ils pas plus communs?


94

Une des caractéristiques des langages fonctionnels qui me manque est l'idée que les opérateurs ne sont que des fonctions. L'ajout d'un opérateur personnalisé est souvent aussi simple que l'ajout d'une fonction. De nombreux langages procéduraux autorisent les surcharges d'opérateurs. Ainsi, dans un certain sens, les opérateurs sont toujours des fonctions (c'est très vrai dans D où l'opérateur est passé en tant que chaîne dans un paramètre de modèle).

Il semble que, lorsque la surcharge d'opérateurs est autorisée, il est souvent trivial d'ajouter des opérateurs personnalisés supplémentaires. J'ai trouvé cet article de blog qui explique que les opérateurs personnalisés ne fonctionnent pas bien avec la notation infix en raison des règles de priorité, mais l'auteur propose plusieurs solutions à ce problème.

J'ai regardé autour de moi et je n'ai trouvé aucun langage de procédure prenant en charge des opérateurs personnalisés dans ce langage. Il y a des hacks (comme les macros en C ++), mais ce n'est pas la même chose que le support du langage.

Puisque cette fonctionnalité est assez simple à implémenter, pourquoi n'est-elle pas plus courante?

Je comprends que cela peut conduire à un code laid, mais cela n’a pas empêché les concepteurs de langages d’ajouter des fonctionnalités utiles qui peuvent être facilement utilisées (macros, opérateur ternaire, pointeurs non sécurisés).

Cas d'utilisation réels:

  • Implémente les opérateurs manquants (par exemple, Lua n'a pas d'opérateurs au niveau des bits)
  • Mimic D's ~(concaténation de tableaux)
  • DSL
  • Utiliser |comme sucre de syntaxe de style de tuyau Unix (à l'aide de coroutines / générateurs)

Je suis aussi intéressé par les langues qui font permettre aux opérateurs personnalisés, mais je suis plus intéressé pourquoi il a été exclu. J'ai envisagé de forger un langage de script pour ajouter des opérateurs définis par l'utilisateur, mais je me suis arrêté quand je me suis rendu compte que je ne l'avais vu nulle part. Il y a donc probablement une bonne raison pour laquelle les concepteurs de langage plus intelligents que moi ne l'ont pas permis.


4
Il y a une discussion Reddit sur cette question.
Dynamic

2
@dimatura: Je ne connais pas grand chose à propos de R, mais ses opérateurs personnalisés ne semblent toujours pas très flexibles (par exemple, aucune façon correcte de définir la fixité, la portée, la surcharge, etc.), ce qui explique leur faible utilisation. C'est différent dans d'autres langues, notamment en Haskell, qui utilise beaucoup d' opérateurs d'infixes personnalisés . Nimrod est un autre exemple de langage procédural avec une prise en charge raisonnablement personnalisée-infixe , et bien sûr, Perl le permet également .
leftaroundabout

4
Il existe une autre option: Lisp n’a aucune différence entre les opérateurs et les fonctions.
BeardedO

4
Pas encore de mention de Prolog, un autre langage dans lequel les opérateurs ne sont que du sucre syntaxique pour les fonctions (oui, même en mathématiques) et qui vous permet de définir des opérateurs personnalisés avec une priorité personnalisée.
David Cowden

2
@BeardedO, il n'y a pas du tout d'opérateurs infixés dans Lisp. Une fois que vous les présentez, vous devrez traiter tous les problèmes avec la priorité et autres.
SK-logic le

Réponses:


134

Il existe deux écoles de pensée diamétralement opposées dans la conception du langage de programmation. La première est que les programmeurs écrivent un meilleur code avec moins de restrictions, et l’autre est qu’ils écrivent un meilleur code avec davantage de restrictions. À mon avis, la réalité est que les bons programmeurs expérimentés s'épanouissent avec moins de restrictions, mais que ces restrictions peuvent bénéficier à la qualité du code des débutants.

Les opérateurs définis par l'utilisateur peuvent créer un code très élégant entre des mains expérimentées et un code vraiment affreux pour un débutant. Donc, si votre langue les inclut ou non, cela dépend de la pensée de votre concepteur de langue.


23
Les deux écoles de pensée, plus.google.com/110981030061712822816/posts/KaSKeg4vQtz , ont également +1 pour avoir la réponse la plus directe (et probablement la plus vraie) à la question de savoir pourquoi les concepteurs de langages choisissent l'une ou l'autre. Je dirais également que, sur la base de votre thèse, vous pourriez extrapoler le fait que moins de langues le permettent, car un plus petit nombre de développeurs sont hautement qualifiés (le pourcentage le plus élevé sera toujours une minorité par définition)
Jimmy Hoffa,

12
Je pense que cette réponse est vague et n’est que marginalement liée à la question. (Je pense aussi que votre description est fausse, car elle contredit mon expérience mais ce n'est pas important.)
Konrad Rudolph

1
Comme @KonradRudolph l'a dit, cela ne répond pas vraiment à la question. Prenez un langage comme Prolog qui vous permet de faire tout ce que vous voulez avec les opérateurs, y compris définir leur priorité lorsqu’un infixe ou préfixe est placé. Je ne pense pas que le fait de pouvoir écrire des opérateurs personnalisés ait quelque chose à voir avec le niveau de compétence du public cible, mais plutôt parce que l'objectif de Prolog est de lire de manière aussi logique que possible. L'inclusion d'opérateurs personnalisés vous permet d'écrire des programmes très lus logiquement (après tout, un programme prologue n'est qu'un tas d'instructions logiques).
David Cowden

Comment ai-je manqué ce stevey rant? Oh mec, favori
George Mauer

Dans quelle philosophie tomberais-je si je pense que les programmeurs sont plus susceptibles d’écrire le meilleur code si les langages leur donnent les outils pour dire quand le compilateur doit faire diverses déductions (par exemple, des arguments de boxing automatique à une Formatméthode) et quand il doit refuser ( p.ex. arguments de boxe automatique à ReferenceEquals). Plus le langage de capacité permet aux programmeurs de dire quand certaines inférences seraient inappropriées, plus il peut offrir des inférences pratiques en toute sécurité, le cas échéant.
Supercat

83

Si vous avez le choix entre concaténer des tableaux avec ~ ou avec "myArray.Concat (secondArray)", je préférerais probablement le dernier. Pourquoi? Parce que ~ est un caractère complètement dépourvu de sens qui n'a que sa signification - celle de la concaténation de tableaux - donnée dans le projet spécifique où il a été écrit.

Comme vous l'avez dit, les opérateurs ne sont fondamentalement pas différents des méthodes. Cependant, bien que des noms lisibles et compréhensibles puissent être attribués aux méthodes, ce qui facilite la compréhension du flux de code, les opérateurs sont opaques et situationnels.

C'est pourquoi je n'aime pas non plus l' .opérateur PHP (concaténation de chaînes) ni la plupart des opérateurs de Haskell ou d'OCaml, bien que dans ce cas, certaines normes universellement acceptées apparaissent pour les langages fonctionnels.


27
La même chose pourrait être dite de tous les opérateurs. Ce qui les rend bons, et peut également rendre bons les opérateurs définis par l'utilisateur (s'ils le sont judicieusement), est la suivante: bien que le caractère utilisé soit simplement un caractère, son utilisation répandue et éventuellement des propriétés mnémoniques en font immédiatement leur signification dans le code source. évident pour ceux qui le connaissent bien. Donc, votre réponse actuelle n’est pas si convaincante à mon humble avis.

33
Comme je l'ai mentionné à la fin, certains opérateurs ont une reconnaissance universelle (tels que les symboles arithmentiques standard, les décalages au niveau du bit, ET / OU et ainsi de suite), de sorte que leur légèreté l'emporte sur leur opacité. Mais être capable de définir des opérateurs arbitraires semble garder le pire des deux mondes.
Avner Shahar-Kashtan

11
Vous êtes donc également en faveur de l'interdiction, par exemple, des noms à une lettre au niveau de la langue? Plus généralement, ne rien laisser dans la langue qui semble faire plus de mal que de bien? C'est une approche valable de la conception linguistique, mais pas la seule, donc je ne pense pas que vous ne pouvez pas la présupposer.

9
Je ne suis pas d'accord pour dire que les opérateurs personnalisés "ajoutent" une fonctionnalité, cela supprime une restriction. Les opérateurs ne sont généralement que des fonctions. Ainsi, au lieu d'une table de symboles statique pour les opérateurs, vous pouvez utiliser une table dynamique, dépendante du contexte. J'imagine que c'est de savoir comment l' opérateur sont les surcharges traitées, car +et <<certainement ne sont pas définis sur Object(je reçois « pas de match pour l' opérateur + en ... » lorsque vous faites que sur une classe nue en C ++).
beatgammit

10
Ce qu’il faut beaucoup de temps pour comprendre, c’est que le codage ne consiste généralement pas à obtenir un ordinateur qui fasse quelque chose pour vous, mais à générer un document lisible par les utilisateurs et les ordinateurs, les utilisateurs étant un peu plus importants que les ordinateurs. Cette réponse est tout à fait correcte, si la personne suivante (ou vous dans 2 ans) doit passer 10 secondes à essayer de comprendre ce que signifie ~, vous auriez peut-être aussi passé 10 secondes à taper un appel de méthode.
Bill K

71

Puisque cette fonctionnalité est assez simple à implémenter, pourquoi n'est-elle pas plus courante?

Votre prémisse est fausse. Ce n'est pas «assez simple à mettre en œuvre». En fait, cela pose de nombreux problèmes.

Jetons un coup d'œil aux «solutions» suggérées dans le post:

  • Aucune préséance . L'auteur lui-même dit: "Ne pas utiliser les règles de priorité n'est tout simplement pas une option."
  • Analyse sémantique consciente . Comme le dit l'article, cela exigerait que le compilateur ait beaucoup de connaissances sémantiques. L'article n'offre pas réellement de solution pour cela et laissez-moi vous dire que ce n'est tout simplement pas trivial. Les compilateurs sont conçus comme un compromis entre puissance et complexité. En particulier, l'auteur mentionne une étape de pré-analyse visant à collecter les informations pertinentes, mais cette dernière est inefficace et les compilateurs s'efforcent vraiment de minimiser les passes d'analyse.
  • Aucun opérateur d'infix personnalisé . Eh bien, ce n'est pas une solution.
  • Solution hybride . Cette solution comporte de nombreux inconvénients (mais pas tous) de l'analyse syntaxique avec prise en compte sémantique. En particulier, étant donné que le compilateur doit traiter les jetons inconnus comme potentiellement des opérateurs personnalisés, il ne peut souvent pas produire de messages d'erreur significatifs. Il peut également être nécessaire que la définition dudit opérateur pour procéder à l'analyse syntaxique (pour collecter des informations de type, etc.), nécessitant à nouveau une passe d'analyse supplémentaire.

Globalement, il s’agit d’une fonctionnalité coûteuse à mettre en œuvre, tant en termes de complexité d’analyseur que de performances, et il n’est pas clair que cela apporterait de nombreux avantages. Bien sûr, la possibilité de définir de nouveaux opérateurs présente certains avantages, mais même ceux-ci sont controversés (il suffit de regarder les autres réponses en affirmant qu’avoir de nouveaux opérateurs n’est pas une bonne chose).


2
Merci pour la voix de la raison. Mon expérience suggère que les personnes qui considèrent que les choses sont insignifiantes sont soit des experts, soit une ignorante allégresse, et plus souvent dans la dernière catégorie: x
Matthieu M.

14
chacun de ces problèmes a été résolu dans des langues qui existent depuis 20 ans ...
Philip JF

11
Et pourtant, sa mise en œuvre est exceptionnellement triviale. Oubliez tous les algorithmes d’analyse de livre de dragon, c’est un XXIe siècle et il est temps de passer à autre chose. Même l'extension d'une syntaxe de langage est simple, et l'ajout d'opérateurs d'une priorité donnée est trivial. Regardez, par exemple, l'analyseur Haskell. Il est beaucoup, beaucoup plus simple que les analyseurs syntaxiques pour les langues gonflées "classiques".
SK-logic

3
@ SK-logic Votre affirmation générale ne me convainc pas. Oui, l'analyse a évolué. Non, l'implémentation de priorités d'opérateurs arbitraires en fonction du type n'est pas «triviale». Produire de bons messages d'erreur avec un contexte limité n'est pas «trivial». Produire des analyseurs syntaxiques efficaces avec des langues nécessitant plusieurs transformations n'est pas réalisable.
Konrad Rudolph

6
En Haskell, l'analyse est "facile" (comparée à la plupart des langues). La priorité est indépendante du contexte. La partie qui vous donne des messages d'erreur concrets est liée à la classe et aux fonctionnalités système avancées, et non aux opérateurs définis par l'utilisateur. C'est la surcharge, pas la définition de l'utilisateur, c'est le problème difficile.
Philip JF

25

Ignorons l'intégralité de l'argument "opérateurs abusés pour nuire à la lisibilité" pour le moment et concentrons-nous sur les implications en termes de conception de langage.

Les opérateurs Infix ont plus de problèmes que de simples règles de priorité (bien que, pour être franc, le lien que vous référencez banalise l'impact de cette décision de conception). La première est la résolution des conflits: que se passe-t-il lorsque vous définissez a.operator+(b)et b.operator+(a)? Préférer l’un sur l’autre conduit à casser la propriété commutative attendue de cet opérateur. Lancer une erreur peut conduire à des modules qui fonctionneraient autrement seraient cassés une fois ensemble. Qu'advient-il lorsque vous commencez à jeter des types dérivés dans le mélange?

Le fait est que les opérateurs ne sont pas que des fonctions. Les fonctions sont autonomes ou appartiennent à leur classe, ce qui indique clairement quel paramètre (le cas échéant) est propriétaire de la répartition polymorphe.

Et cela ne tient pas compte des divers problèmes d’emballage et de résolution posés par les opérateurs. La raison pour laquelle les concepteurs de langages (en gros) limitent la définition d'opérateur infix est parce qu'il crée une pile de problèmes pour le langage tout en offrant des avantages discutables.

Et franchement, car ils ne sont pas triviaux à mettre en œuvre.


1
Je pense que c'est la raison pour laquelle Java ne les inclut pas, mais les langages qui en disposent ont des règles de priorité claires. Je pense que cela ressemble à la multiplication matricielle. Ce n'est pas commutatif, vous devez donc être conscient lorsque vous utilisez des matrices. Je pense que la même chose s’applique lorsqu’il s’agit de classes, mais oui, je suis d’accord pour dire qu’avoir un non-commutatif +est mauvais. Mais est-ce vraiment un argument contre des opérateurs définis par l'utilisateur? Cela semble être un argument contre la surcharge des opérateurs en général.
beatgammit

1
@tjameson - Oui, les langues ont des règles claires pour la priorité, pour les mathématiques . Les règles de priorité pour les opérations mathématiques ne s'appliqueront probablement pas vraiment si les opérateurs sont surchargés, par exemple boost::spirit. Dès que vous autorisez des opérateurs définis par l'utilisateur, la situation empire car il n'existe aucun moyen efficace de définir la priorité des mathématiques. J'ai moi-même écrit un peu à ce sujet dans le contexte d'un langage qui cherche spécifiquement à résoudre les problèmes d'opérateurs définis arbitrairement.
Telastyn

22
J'appelle des conneries, monsieur. Il y a une vie en dehors du ghetto OO et ses fonctions n'appartiennent pas nécessairement à un objet. Par conséquent, trouver la bonne fonction revient exactement à trouver le bon opérateur.
Matthieu M.

1
@matthieuM. - Certainement. Les règles de propriété et de répartition sont plus uniformes dans les langues qui ne prennent pas en charge les fonctions membres. Cependant, à un moment donné, la question initiale devient: "Pourquoi les langues non-OO ne sont-elles pas plus communes?" qui est un tout autre match de baseball.
Telastyn

11
Avez-vous vu comment Haskell gère les opérateurs personnalisés? Ils fonctionnent exactement comme des fonctions normales, sauf qu’ils ont également une priorité associée. (En fait, les fonctions normales peuvent l'être, donc même là, elles ne diffèrent pas vraiment.) Fondamentalement, les opérateurs sont infixés par défaut et les noms sont préfixés, mais c'est la seule différence.
Tikhon Jelvis

19

Je pense que vous seriez surpris de voir combien de fois les surcharges d’opérateurs sont implémentées sous une forme ou une autre. Mais ils ne sont pas couramment utilisés dans beaucoup de communautés.

Pourquoi utiliser ~ pour concaténer un tableau? Pourquoi ne pas utiliser << comme le fait Ruby ? Parce que les programmeurs avec lesquels vous travaillez ne sont probablement pas des programmeurs Ruby. Ou D programmeurs. Alors, que font-ils quand ils rencontrent votre code? Ils doivent aller chercher ce que le symbole signifie.

J'avais l'habitude de travailler avec un très bon développeur C # qui avait également un goût prononcé pour les langages fonctionnels. À l'improviste, il a commencé à introduire les monades en C # à l'aide de méthodes d'extension et en utilisant la terminologie standard des monades . Personne ne pouvait nier que certains de ses codes étaient plus clairs et lisibles encore une fois que l'on savait ce que cela voulait dire, mais cela signifiait que tout le monde devait apprendre la terminologie de la monade avant que le code ne prenne du sens .

Assez juste, vous pensez? Ce n'était qu'une petite équipe. Personnellement, je ne suis pas d'accord. Chaque nouveau développeur était destiné à être dérouté par cette terminologie. N'avons-nous pas assez de problèmes pour apprendre un nouveau domaine?

D'un autre côté, j'utiliserai volontiers l' ??opérateur en C # car je m'attends à ce que les autres développeurs C # sachent de quoi il s'agit, mais je ne le surchargerais pas dans un langage qui ne le prendrait pas en charge par défaut.


Je ne comprends pas votre exemple Double.NaN, ce comportement fait partie de la spécification de virgule flottante et est pris en charge dans toutes les langues que j'ai utilisées. Voulez-vous dire que les opérateurs personnalisés ne sont pas pris en charge car cela confondre les développeurs qui ne connaissent pas la fonctionnalité? Cela ressemble au même argument contre l'utilisation de l'opérateur ternaire ou de votre ??exemple.
beatgammit

@tjameson: Vous avez raison, en fait, c'était un peu tangentiel à ce que j'essayais de dire, et pas particulièrement bien écrit. J'ai pensé à la ?? exemple comme je l'écrivais et préfère cet exemple. Suppression du paragraphe double.NaN.
pdr

6
Je pense que votre point "chaque nouveau développeur était voué à être dérouté par cette terminologie" est un peu terne, s'inquiéter de la courbe d'apprentissage des nouveaux développeurs de votre code est le même que m'inquiéter de la courbe d'apprentissage des nouveaux développeurs à nouvelle version de .NET. Je pense que le plus important est que lorsque les développeurs l'apprennent (nouveau .NET ou votre base de code), cela a-t-il un sens et est-il utile? Si tel est le cas, la courbe d'apprentissage en vaut la peine, car chaque développeur n'a besoin de l'apprendre qu'une seule fois, bien que le code doive être manipulé d'innombrables fois. Par conséquent, s'il améliore le code, son coût est minime.
Jimmy Hoffa

7
@ JimmyHoffa C'est un coût récurrent. Paiement initial pour chaque nouveau développeur et impact sur les développeurs existants car ils doivent le prendre en charge. Il y a aussi un coût de documentation et un élément de risque - on se sent assez en sécurité aujourd'hui, mais dans 30 ans, tout le monde sera parti, le langage et l'application sont désormais "hérités", la documentation est une pile volumineuse et il restera un pauvre suceur en train de se couper les cheveux à la brillance de programmeurs trop paresseux pour taper ".concat ()". La valeur attendue est-elle suffisante pour compenser les coûts?
Sylverdrag

3
En outre, l'argument "Tous les nouveaux développeurs étaient voués à être déroutés par cette terminologie. N'avons-nous pas assez de problèmes pour apprendre un nouveau domaine?" aurait pu être appliqué à la programmation orientée objet dans les années 80, à la programmation structurée auparavant ou à l'IoC il y a 10 ans. Il est temps d'arrêter d'utiliser cet argument fallacieux.
Mauricio Scheffer

11

Je peux penser à plusieurs raisons:

  • Elles ne sont pas simples à implémenter - autoriser des opérateurs personnalisés arbitraires peut rendre votre compilateur beaucoup plus complexe, en particulier si vous autorisez les règles de priorité, de fixité et d'arité définies par l'utilisateur. Si la simplicité est une vertu, la surcharge de l’opérateur vous éloigne de la bonne conception du langage.
  • Ils sont victimes d'abus - principalement de la part des codeurs qui pensent qu'il est "cool" de redéfinir les opérateurs et commencent à les redéfinir pour toutes sortes de classes personnalisées. Bientôt, votre code est encombré d'une multitude de symboles personnalisés que personne ne peut lire ni comprendre, car les opérateurs ne respectent pas les règles classiques bien comprises. Je n'achète pas l'argument "DSL", à moins que votre DSL ne soit un sous-ensemble des mathématiques :-)
  • Ils gênent la lisibilité et la maintenabilité - si les opérateurs sont régulièrement remplacés, il peut devenir difficile de repérer l'utilisation de cette installation, et les codeurs sont obligés de se demander en permanence ce que fait l'opérateur. Il est préférable de donner des noms de fonction significatifs. Taper quelques caractères en trop n'est pas cher, les problèmes de maintenance à long terme sont chers.
  • Ils peuvent casser les attentes de performance implicites . Par exemple, je m'attendrais normalement à ce que la recherche d'un élément dans un tableau soit O(1). Mais avec la surcharge d’opérateur, someobject[i]une O(n)opération pourrait facilement dépendre de la mise en œuvre de l’opérateur d’indexation.

En réalité, il y a très peu de cas où la surcharge des opérateurs a des utilisations justifiables par rapport à l'utilisation de fonctions standard. Un exemple légitime pourrait être la conception d’une classe de nombres complexes à l’usage des mathématiciens, qui comprennent les méthodes bien comprises de définition des opérateurs mathématiques pour les nombres complexes. Mais ce n'est vraiment pas un cas très commun.

Quelques cas intéressants à considérer:

  • Lisps : en général, ne faites pas de distinction entre les opérateurs et les fonctions - +c'est juste une fonction régulière. Vous pouvez définir les fonctions à votre guise (il existe généralement un moyen de les définir dans des espaces de noms distincts pour éviter tout conflit avec les fonctions intégrées +), y compris les opérateurs. Mais il existe une tendance culturelle à utiliser des noms de fonction significatifs, de sorte que les abus ne sont pas trop importants. En outre, dans Lisp, la notation de préfixe a tendance à être utilisée exclusivement, de sorte que le "sucre syntaxique" a moins de valeur que les surcharges d’opérateurs fournissent.
  • Java - interdit la surcharge des opérateurs. C’est parfois gênant (pour des choses comme le cas des nombres complexes), mais en moyenne, c’est probablement la bonne décision de conception pour Java, qui est conçu comme un langage simple et polyvalent pour la programmation orientée objet. Le code Java est en fait assez facile à maintenir pour les développeurs peu ou moyennement qualifiés, grâce à cette simplicité.
  • C ++ a une surcharge d'opérateur très sophistiquée. Cela peut parfois être abusé ( cout << "Hello World!"n'importe qui?), Mais l'approche est logique étant donné le positionnement de C ++ en tant que langage complexe qui permet une programmation de haut niveau tout en vous permettant de vous rapprocher du métal pour la performance, vous permettant par exemple d'écrire une classe de nombres complexe qui se comporte exactement comme vous le souhaitez sans compromettre les performances. Il est entendu que c'est votre propre responsabilité si vous vous tirez une balle dans le pied.

8

Puisque cette fonctionnalité est assez simple à implémenter, pourquoi n'est-elle pas plus courante?

Ce n'est pas trivial à implémenter (sauf si trivialement implémenté). De plus, cela ne vous rapporte pas beaucoup, même si elle est mise en œuvre de manière idéale: les gains de lisibilité résultant de la nuance sont compensés par les pertes de lisibilité dues à la non-familiarité et à l'opacité. En bref, c'est peu commun parce que cela ne vaut généralement pas le temps des développeurs ou des utilisateurs.

Cela dit, je peux penser à trois langues qui le font, et elles le font de différentes manières:

  • Racket, un schéma, quand il n’est pas tout à fait S-expression-y permet et s’attend à ce que vous écriviez ce qui équivaut à un analyseur syntaxique pour toute syntaxe que vous souhaitez étendre (et fournit des points d’accès utiles pour rendre ce tractable).
  • Haskell, un langage de programmation purement fonctionnel, permet de définir tout opérateur composé uniquement de ponctuation et de fournir un niveau de fixité (10 disponibles) et une associativité. Des opérateurs etc. ternaires peuvent être créés à partir d'opérateurs binaires et de fonctions d'ordre supérieur.
  • Agda, un langage de programmation typé de manière dépendante, est extrêmement flexible avec des opérateurs (papier ici ) permettant de définir à la fois les opérateurs if-then et if-then-then-else dans le même programme, mais son lexer, son analyseur et son évaluateur sont tous fortement couplés Par conséquent.

4
Vous avez dit que "ce n'est pas trivial" et vous avez immédiatement répertorié trois langages avec des implémentations outrageusement triviales. Donc, c'est assez trivial après tout, n'est-ce pas?
SK-logic le

7

Une des principales raisons pour lesquelles les opérateurs personnalisés sont découragés est qu’aucun opérateur ne peut / ne peut rien faire.

Par exemple cstream, on a beaucoup critiqué la surcharge de l'équipe de gauche.

Lorsqu'un langage autorise les surcharges d'opérateur, il est généralement encouragé de garder le comportement de l'opérateur similaire au comportement de base afin d'éviter toute confusion.

De plus, les opérateurs définis par l'utilisateur rendent l'analyse beaucoup plus difficile, en particulier lorsqu'il existe également des règles de préférence personnalisées.


6
Je ne vois pas comment les parties sur la surcharge d'opérateurs s'appliquent à la définition d'opérateurs complètement nouveaux.

J'ai vu un très bon usage de la surcharge d'opérateur (bibliothèques d'algèbre linéaire) et très mauvais comme vous l'avez mentionné. Je ne pense pas que ce soit un bon argument contre les opérateurs personnalisés. L'analyse syntaxique peut être un problème, mais il est assez évident qu'un opérateur est attendu. Après l'analyse, il appartient au générateur de code de rechercher le sens de l'opérateur.
beatgammit

3
Et en quoi est-ce différent de la surcharge de méthodes?
Jörg W Mittag

@ JörgWMittag avec surcharge de méthode, il y a (la plupart du temps) un nom explicite attaché. avec un symbole, il est plus difficile d'expliquer succinctement ce qui va arriver
Ratchet freak

1
@ Ratchetfreak: Eh bien. +ajoutez deux choses, -soustrayez-les, *multipliez-les. Mon sentiment est que personne ne force le programmeur à faire en sorte que la fonction / méthode addajoute quelque chose et doNothingpuisse lancer des armes nucléaires. Et a.plus(b.minus(c.times(d)).times(e)est beaucoup moins lisible que a + (b - c * d) * e(bonus supplémentaire - où la première piqûre est une erreur de transcription). Je ne vois pas en quoi le premier est plus significatif ...
Maciej Piechotka

4

Nous n'utilisons pas d'opérateurs définis par l'utilisateur pour la même raison que nous n'utilisons pas de mots définis par l'utilisateur. Personne n'appellerait leur fonction "sworp". La seule façon de transmettre votre pensée à une autre personne est d’utiliser un langage partagé. Et cela signifie que les mots et les signes (opérateurs) doivent être connus de la société pour qui vous écrivez votre code.

Par conséquent, les opérateurs que vous voyez utilisés dans les langages de programmation sont ceux que nous avons appris à l’école (arithmétique) ou ceux qui ont été établis dans la communauté de programmation, comme par exemple les opérateurs booléens.


1
Ajoutez à cela la possibilité de découvrir le sens d'une fonction. Dans le cas de "sworp", vous pouvez très facilement le coller dans un champ de recherche et trouver sa documentation. Coller un opérateur dans un moteur de recherche est un tout autre parc. Nos moteurs de recherche actuels sont tout simplement nuls pour trouver des opérateurs, et j'ai perdu beaucoup de temps à essayer de trouver le sens dans certains cas.
Mark H

1
Combien "d'école" comptez-vous? Par exemple, je pense que elemest une excellente idée et que tout le monde devrait comprendre un opérateur, mais d’autres semblent ne pas être d’accord.
Tikhon Jelvis

1
Ce n'est pas le problème avec le symbole. C'est le problème avec le clavier. Par exemple, j'utilise emacs haskell-mode avec l'embellisseur unicode activé. Et il convertit automatiquement les opérateurs ascii en symboles Unicode. Mais je ne serais pas capable de le taper s'il n'y avait pas un équivalent Ascii.
Vagif Verdi

2
Les langages naturels sont constitués des "mots définis par l'utilisateur" et de rien d'autre. Et certaines langues encouragent réellement la formation de mots personnalisés.
SK-logic le

4

En ce qui concerne les langages qui supportent une telle surcharge: Scala le fait, de manière beaucoup plus propre et meilleure peut utiliser le langage C ++. La plupart des caractères peuvent être utilisés dans les noms de fonction, vous pouvez donc définir des opérateurs tels que! + * = ++, si vous le souhaitez. Il existe un support intégré pour infixe (pour toutes les fonctions prenant un argument). Je pense que vous pouvez également définir l’associativité de telles fonctions. Vous ne pouvez cependant pas définir la priorité (uniquement avec des tours laides, voir ici ).


4

Une chose qui n’a pas encore été mentionnée est le cas de Smalltalk, où tout (y compris les opérateurs) est un message envoyé. Les "opérateurs" comme +, |etc., sont en réalité des méthodes unaires.

Toutes les méthodes peuvent être remplacées. Cela a + bsignifie donc l' addition d'entiers si aet bsont deux entiers, et l'ajout de vecteurs s'ils sont tous deux OrderedCollections.

Il n'y a pas de règles de priorité, car ce ne sont que des appels de méthodes. Ceci a une implication importante pour la notation mathématique standard: 3 + 4 * 5moyen (3 + 4) * 5, pas 3 + (4 * 5).

(Il s'agit d'un obstacle majeur pour les débutants Smalltalk. Briser les règles mathématiques supprime un cas particulier, de sorte que toute l'évaluation du code se déroule de manière uniforme de gauche à droite, ce qui simplifie énormément le langage.)


3

Vous vous battez contre deux choses ici:

  1. Pourquoi les opérateurs existent-ils dans les langues en premier lieu?
  2. Quelle est la vertu des opérateurs sur les fonctions / méthodes?

Dans la plupart des langues, les opérateurs ne sont pas vraiment implémentés en tant que fonctions simples. Ils peuvent avoir un échafaudage de fonctions, mais le compilateur / runtime est explicitement conscient de leur signification sémantique et de la manière de les traduire efficacement en code machine. Cela est beaucoup plus vrai même par rapport aux fonctions intégrées (c'est pourquoi la plupart des implémentations n'incluent pas non plus tout le temps système d'appel de fonction dans leur implémentation). La plupart des opérateurs sont des abstractions de niveau supérieur sur des instructions primitives trouvées dans les CPU (ce qui explique en partie pourquoi la plupart des opérateurs sont arithmétiques, booléens ou au niveau des bits). Vous pouvez les modéliser en tant que fonctions "spéciales" (appelez-les "primitives" ou "fonctions intégrées" ou "natives" ou autre), mais cela nécessite généralement un ensemble très robuste de sémantiques pour la définition de telles fonctions spéciales. L'alternative consiste à avoir des opérateurs intégrés ressemblant sémantiquement à des opérateurs définis par l'utilisateur, mais invoquant sinon des chemins spéciaux dans le compilateur. Cela va à l’encontre de la réponse à la deuxième question ...

Outre le problème de traduction automatique mentionné ci-dessus, les opérateurs ne sont pas vraiment différents des fonctions au niveau syntaxique. Leurs caractéristiques distinctives tendent à être qu'elles sont concises et symboliques, ce qui laisse entrevoir une caractéristique supplémentaire importante dont elles doivent avoir besoin pour être utiles: elles doivent avoir une signification / sémantique largement comprise par les développeurs. Les symboles courts ne donnent pas beaucoup de sens à moins que ce ne soit un raccourci pour un ensemble de sémantiques déjà compris. Cela rend les opérateurs définis par l'utilisateur inutiles par nature, car de par leur nature même, ils ne sont pas compris de manière très large. Ils ont autant de sens qu'un nom de fonction à une ou deux lettres.

Les surcharges d’opérateurs de C ++ fournissent un terrain fertile pour l’examiner. La plupart des "abus" d'opérateurs se présentent sous la forme de surcharges qui rompent une partie du contrat sémantique qui est largement compris (un exemple classique est une surcharge d'opérateur + telle que a + b! = B + a, ou où + modifie l'une de ses opérandes).

Si vous examinez Smalltalk, qui autorise la surcharge d'opérateurs et les opérateurs définis par l'utilisateur, vous pouvez voir comment une langue peut s'y prendre et son utilité. Dans Smalltalk, les opérateurs sont simplement des méthodes ayant différentes propriétés syntaxiques (à savoir, elles sont codées en tant qu'infix binary). Le langage utilise des "méthodes primitives" pour des opérateurs et méthodes accélérés spéciaux. Vous constatez que peu ou pas d'opérateurs définis par l'utilisateur sont créés, et lorsqu'ils le sont, ils ont tendance à ne pas être utilisés autant que l'auteur le leur a probablement destinés. Même l'équivalent d'une surcharge d'opérateur est rare, car définir une nouvelle fonction en tant qu'opérateur plutôt qu'en tant que méthode est une perte nette, car cette dernière permet l'expression de la sémantique de la fonction.


1

J'ai toujours trouvé que les surcharges d'opérateurs en C ++ constituaient un raccourci commode pour une équipe composée d'un développeur unique, mais provoquaient toute une confusion sur le long terme simplement parce que les appels de méthode étaient "masqués" d'une manière qui n'était pas facile. pour que des outils comme le doxygen se séparent et que les gens aient besoin de comprendre les idiomes pour pouvoir les utiliser correctement.

Parfois, il est beaucoup plus difficile de comprendre que ce à quoi vous vous attendiez, même. Il était une fois, dans un grand projet C ++ multiplate-forme, la décision de normaliser la construction des chemins en créant un FilePathobjet (similaire à l' Fileobjet Java ), qui aurait pour opérateur / utilisé de concaténer un autre partie de chemin sur elle (afin que vous puissiez faire quelque chose comme File::getHomeDir()/"foo"/"bar"et il ferait la bonne chose sur toutes nos plates-formes prises en charge). Tous ceux qui l'ont vu diraient essentiellement: "Qu'est-ce que l'enfer? Division de cordes? ... Oh, c'est mignon, mais je ne lui fais pas confiance pour faire la bonne chose."

De même, il existe de nombreux cas en programmation graphique ou dans d’autres domaines où les mathématiques vectorielles / matricielles se produisent souvent dans des situations où il est tentant de faire des choses comme Matrix * Matrix, Vector * Vector (point), Vector% Vector (croix), Matrix * Vector ( transformée matricielle), Matrix ^ Vector (transformée matricielle dans les cas spéciaux en ignorant la coordonnée homogène - utile pour les normales à la surface), etc., mais elle économise un peu de temps d'analyse pour la personne qui a écrit la bibliothèque de mathématiques vectorielles. confondant la question plus pour les autres. Ça n'en vaut pas la peine.


0

Les surcharges d’opérateurs sont une mauvaise idée, pour la même raison que les surcharges de méthodes: un même symbole à l’écran aurait des significations différentes en fonction de ce qui les entoure. Cela rend plus difficile la lecture occasionnelle.

Étant donné que la lisibilité est un aspect critique de la maintenabilité, vous devez toujours éviter les surcharges (sauf dans des cas très particuliers). Il est de loin préférable que chaque symbole (qu’il s’agisse d’un opérateur ou d’un identificateur alphanumérique) ait une signification unique.

Pour illustrer ceci: lors de la lecture d'un code non familier, si vous rencontrez un nouvel identificateur alphanum que vous ne connaissez pas, vous avez au moins l'avantage de savoir que vous ne le connaissez pas.. Vous pouvez ensuite aller le chercher. Si, toutefois, vous voyez un identifiant ou un opérateur commun dont vous connaissez la signification, vous aurez beaucoup moins de chances de remarquer qu'il a été surchargé pour avoir une signification complètement différente. Pour savoir quels opérateurs ont été surchargés (dans une base de code généralisant la surcharge), vous devez avoir une connaissance pratique du code complet, même si vous ne voulez en lire qu'une petite partie. Cela empêcherait les nouveaux développeurs de se familiariser avec ce code et empêcherait les gens de faire un petit travail. Cela peut s'avérer utile pour la sécurité d'emploi des programmeurs, mais si vous êtes responsable du succès de la base de code, vous devez éviter cette pratique à tout prix.

Étant donné que les opérateurs sont de petite taille, les opérateurs surchargés autoriseraient un code plus dense, mais rendre le code dense ne constitue pas un réel avantage. Une ligne avec deux fois plus de logique prend deux fois plus de temps à lire. Le compilateur s'en fiche. Le seul problème est la lisibilité humaine. Étant donné que rendre le code compact n'améliore pas la lisibilité, la compacité ne présente aucun avantage réel. Allez-y, prenez l'espace et attribuez un identifiant unique aux opérations uniques. Votre code aura plus de succès à long terme.


"le même symbole à l'écran aurait des significations différentes en fonction de ce qui l'entoure" - c'est déjà le cas pour de nombreux opérateurs dans la plupart des langues.
rkj

-1

Des difficultés techniques pour gérer la priorité et l'analyse complexe mise de côté, je pense que certains aspects de ce qu'est un langage de programmation doivent être pris en compte.

Les opérateurs sont généralement des constructions logiques courtes, bien définies et documentées dans le langage de base (comparer, assigner, etc.). Ils sont aussi généralement difficiles à comprendre sans documentation (à comparer a^bpar xor(a,b)exemple). Il y a un nombre assez limité d'opérateurs qui pourraient réellement avoir un sens dans la programmation normale (>, <, =, + etc ..).

Mon idée est qu'il vaut mieux s'en tenir à un ensemble d'opérateurs bien définis dans un langage, puis autoriser la surcharge de ces opérateurs (dans la mesure où il est recommandé aux opérateurs de faire la même chose, mais avec un type de données personnalisé).

Vos cas d'utilisation de ~et |seraient réellement possibles avec une surcharge d'opérateur simple (C #, C ++, etc.). DSL est un domaine d'utilisation valide, mais probablement l'un des seuls domaines valides (de mon point de vue). Je pense cependant qu'il existe de meilleurs outils pour créer de nouveaux langages au sein de celui-ci. L'exécution d'un vrai langage DSL dans un autre langage n'est pas si difficile en utilisant l'un de ces outils compilateur-compilateur. Il en va de même pour "l'argument d'extension LUA". Une langue est probablement définie principalement pour résoudre des problèmes d’une manière spécifique, et non pour servir de base à des sous-langues (des exceptions existent).


-1

Un autre facteur à prendre en compte est qu’il n’est pas toujours simple de définir une opération avec les opérateurs disponibles. Je veux dire, oui, pour tout type de numéro, l'opérateur '*' peut avoir un sens, et est généralement implémenté dans le langage ou dans les modules existants. Mais dans le cas des classes complexes typiques que vous devez définir (choses telles que ShipingAddress, WindowManager, ObjectDimensions, PlayerCharacter, etc.), ce comportement n'est pas clair ... Que signifie ajouter ou soustraire un nombre à une adresse? Multiplier deux adresses?

Bien sûr, vous pouvez définir que l'ajout d'une chaîne à une classe ShippingAddress signifie une opération personnalisée telle que "remplacer la ligne 1 dans l'adresse" (au lieu de la fonction "setLine1") et l'ajout d'un nombre correspond à "remplacer le code postal" (au lieu de "setZipCode") , mais alors le code n’est pas très lisible et déroutant. Nous pensons généralement que les opérateurs sont utilisés dans les types / classes de base, car leur comportement est intuitif, clair et cohérent (une fois que vous maîtrisez le langage, au moins). Pensez dans les types tels que Integer, String, ComplexNumbers, etc.

Ainsi, même si la définition des opérateurs peut être très utile dans certains cas spécifiques, leur mise en œuvre dans le monde réel est assez limitée, dans la mesure où 99% des cas dans lesquels cela sera clairement gagné sont déjà implémentés dans le package de langue de base.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.