Si je veux comparer deux nombres (ou d'autres entités bien ordonnées), je le ferais avec x < y
. Si je veux comparer trois d'entre eux, l'étudiant en algèbre du secondaire suggérera d'essayer x < y < z
. Le programmeur en moi répondra alors par "non, ce n'est pas valide, vous devez le faire x < y && y < z
".
La plupart des langues que j'ai rencontrées ne semblent pas supporter cette syntaxe, ce qui est étrange compte tenu de sa fréquence en mathématiques. Python est une exception notable. JavaScript ressemble à une exception, mais il ne s'agit en réalité que d'un sous-produit regrettable de la priorité des opérateurs et des conversions implicites; dans node.js, 1 < 3 < 2
évalue true
, car c’est vraiment (1 < 3) < 2 === true < 2 === 1 < 2
.
Ma question est donc la suivante: pourquoi n’est-il x < y < z
pas couramment disponible dans les langages de programmation, avec la sémantique attendue?
static bool IsInRange<T>(this T candidate, T lower, T upper) where T : IComparable<T>
si cela vous dérangeait de voir ça&&
)Réponses:
Ce sont des opérateurs binaires qui, lorsqu'ils sont chaînés, produisent normalement et naturellement un arbre de syntaxe abstrait comme:
Lors de l'évaluation (ce que vous faites depuis le début), cela produit un résultat booléen
x < y
, vous obtenez alors une erreur de type en essayant de le faireboolean < z
. Afinx < y < z
de fonctionner comme vous avez discuté, vous devez créer un cas spécial dans le compilateur pour produire un arbre de syntaxe tel que:Non pas que ce ne soit pas possible. C'est évidemment le cas, mais cela ajoute une certaine complexité à l'analyseur pour une affaire qui n'arrive pas souvent. En gros, vous créez un symbole qui agit parfois comme un opérateur binaire et parfois efficacement, comme un opérateur ternaire, avec toutes les implications du traitement des erreurs et autres. Cela laisse beaucoup de place aux problèmes que les concepteurs de langage préfèrent éviter si possible.
la source
x<y<z
signifie, ou plus important encorex<y<=z
. Cette réponse s'interprètex<y<z
comme un opérateur trinaire. C’est exactement ainsi que cette expression mathématique bien définie ne doit pas être interprétée.x<y<z
est plutôt un raccourci pour(x<y)&&(y<z)
. Les comparaisons individuelles sont encore binaires.Pourquoi n'est-il
x < y < z
pas couramment disponible dans les langages de programmation?Dans cette réponse, je conclus que
introduction
Je peux parler du point de vue d'un pythoniste sur cette question. Je suis un utilisateur d'une langue avec cette fonctionnalité et j'aime bien étudier les détails de la mise en œuvre de la langue. Au-delà de cela, je connais un peu le processus de changement de langages tels que C et C ++ (la norme ISO est régie par un comité et mis à jour par année.) Et j'ai vu à la fois Ruby et Python mettre en œuvre des modifications radicales.
Documentation et implémentation de Python
Dans la documentation / grammaire, nous voyons que nous pouvons chaîner un nombre quelconque d'expressions avec des opérateurs de comparaison:
et la documentation indique en outre:
Equivalence Logique
Alors
est logiquement équivalent en termes d’évaluation de
x
,y
etz
, à l’exception d’uney
évaluation deux fois:Là encore, la différence est que y n’est évalué qu’une fois avec
(x < y <= z)
.(Notez que les parenthèses sont complètement inutiles et redondantes, mais je les ai utilisées au profit de celles venant d'autres langues, et le code ci-dessus est assez légal en Python.)
Inspection de l'arbre de syntaxe abstraite analysé
Nous pouvons vérifier comment Python analyse les opérateurs de comparaison chaînés:
Nous pouvons donc voir que ce n'est vraiment pas difficile à analyser pour Python ou tout autre langage.
Et contrairement à la réponse actuellement acceptée, l’opération ternaire est une opération de comparaison générique, qui prend la première expression, une itérable de comparaisons spécifiques et une itérable de noeuds d’expression à évaluer si nécessaire. Simple.
Conclusion sur Python
Personnellement, je trouve la sémantique de la gamme assez élégante et la plupart des professionnels de Python que je connais encourageraient plutôt l’utilisation de cette fonctionnalité, au lieu de la considérer comme préjudiciable - la sémantique est clairement énoncée dans la documentation réputée (comme indiqué ci-dessus).
Notez que le code est lu beaucoup plus qu'il n'est écrit. Les modifications qui améliorent la lisibilité du code doivent être acceptées et non ignorées en soulevant des spectres génériques de peur, d’incertitude et de doute .
Alors, pourquoi x <y <z n'est-il pas couramment disponible dans les langages de programmation?
Je pense qu'il y a une convergence de raisons qui sont centrées sur l'importance relative de la fonctionnalité et la relative impulsion / inertie de changement permise par les gouverneurs des langues.
Des questions similaires peuvent être posées sur d'autres fonctionnalités linguistiques plus importantes
Pourquoi l'héritage multiple n'est-il pas disponible en Java ou en C #? Il n'y a pas de bonne réponse ici à l'une ou l'autre question . Les développeurs étaient peut-être trop paresseux, comme le prétend Bob Martin, et les raisons données ne sont que des excuses. Et l'héritage multiple est un sujet assez important en informatique. C'est certainement plus important que le chaînage des opérateurs.
Des solutions simples existent
Le chaînage des opérateurs de comparaison est élégant, mais nullement aussi important que l'héritage multiple. Et tout comme Java et C # ont des interfaces comme solution de contournement, il en va de même pour chaque langage permettant des comparaisons multiples - vous chaînez simplement les comparaisons avec des booléens "et" s, ce qui fonctionne assez facilement.
La plupart des langues sont gouvernées par un comité
La plupart des langues évoluent par comité (plutôt que d’avoir un dictateur bienveillant pour la vie, comme celui de Python). Et je suppose que cette question n’a tout simplement pas été suffisamment appuyée pour pouvoir en sortir de ses comités respectifs.
Les langues qui n'offrent pas cette fonctionnalité peuvent-elles changer?
Si une langue le permet
x < y < z
sans la sémantique mathématique attendue, cela constituerait un changement radical. Si cela ne le permettait pas en premier lieu, il serait presque trivial d'ajouter.Briser les changements
En ce qui concerne les langues avec les changements les plus récents: nous mettons à jour les langages avec les changements de comportement, mais les utilisateurs ont tendance à ne pas aimer cela, en particulier les utilisateurs de fonctionnalités qui peuvent être cassées. Si un utilisateur se fie au comportement précédent de
x < y < z
, il protestera probablement fort. Et comme la plupart des langues sont gouvernées par un comité, je doute que nous aurions beaucoup de volonté politique pour soutenir un tel changement.la source
x < y < z
à(x < y) && (y < z)
. En choisissant des lentes, le modèle mental pour la comparaison chaînée est le calcul général. La comparaison classique n’est pas la mathématique en général, mais la logique booléenne.x < y
produit une réponse binaire{0}
.y < z
produit une réponse binaire{1}
.{0} && {1}
produit la réponse descriptive. La logique est composée, pas naïvement chaînée.x<y<z
. Une langue a déjà eu la chance d’obtenir quelque chose de semblable, et une chance est au début de la langue.I have watched both Ruby and Python implement breaking changes.
Pour ceux qui sont curieux, voici un changement radical en C # 5.0 impliquant des variables de boucle et des fermetures: blogs.msdn.microsoft.com/ericlippert/2009/11/12/…Les langages informatiques tentent de définir les unités les plus petites possibles et vous permettent de les combiner. La plus petite unité possible serait quelque chose comme "x <y" qui donne un résultat booléen.
Vous pouvez demander un opérateur ternaire. Un exemple serait x <y <z. Maintenant, quelles combinaisons d'opérateurs permettons-nous? Évidemment, x> y> z ou x> = y> = z ou x> y> = z ou peut-être que x == y == z devrait être autorisé. Qu'en est-il de x <y> z? x! = y! = z? Que signifie le dernier, x! = Y et y! = Z ou que tous les trois sont différents?
Maintenant, promotion des arguments: en C ou C ++, les arguments seraient promus en un type commun. Alors qu'est-ce que x <y <z signifie sur x est double, mais y et z sont long long int? Tous trois promus de doubler? Ou y est pris double une fois et aussi longtemps l'autre fois? Que se passe-t-il si en C ++ l'un des opérateurs ou les deux sont surchargés?
Et enfin, permettez-vous un nombre quelconque d'opérandes? Comme un <b> c <d> e <f> g?
Eh bien, tout devient très compliqué. Maintenant, ce qui ne me dérangerait pas, c’est que x <y <z produise une erreur de syntaxe. Parce que son utilité est faible comparée aux dégâts causés aux débutants qui ne peuvent pas comprendre ce que fait x <y <z.
la source
x + y + z
, avec la seule différence que cela n'implique aucune différence sémantique. C'est donc qu'aucune langue bien connue ne s'est jamais souciée de le faire.x < y < z
être équivalent à,((x < y) and (y < z))
mais avecy
seulement évalué une fois ) que j’imagine que les langages compilés optimisent leur cheminement. "Parce que son utilité est faible comparée aux dommages causés aux débutants qui ne peuvent pas comprendre ce que fait x <y <z." Je pense que c'est incroyablement utile. Je vais sans doute -1 pour ça ...a < b > c < d > e < f > g
, avec le sens "évident"(a < b) and (b > c) and (c < d) and (d > e) and (e < f) and (f > g)
. Ce n’est pas parce que vous pouvez écrire que vous devriez. L'élimination de ces monstruosités relève de la révision du code. D'autre part, l'écriture0 < x < 8
en python a l'évidence (pas de guillemets effrayants), ce qui signifie que x est compris entre 0 et 8, exclusif.Dans de nombreux langages de programmation,
x < y
une expression binaire accepte deux opérandes et donne un résultat booléen unique. Par conséquent, si enchaînant plusieurs expressions,true < z
etfalse < z
ne sera pas de sens, et si ces expressions sont avec succès, ils sont susceptibles de produire un résultat erroné.Il est beaucoup plus facile de penser
x < y
à un appel de fonction qui prend deux paramètres et produit un seul résultat booléen. En fait, c'est le nombre de langues implémentées sous le capot. C'est composable, facilement compilable, et ça marche.Le
x < y < z
scénario est beaucoup plus compliqué. Maintenant , le compilateur, en effet, a à la mode trois fonctions:x < y
,y < z
et le résultat de ces deux valeurs ANDED ensemble, le tout dans le cadre d'une sans doute la grammaire du langage ambigu .Pourquoi l'ont-ils fait dans l'autre sens? Parce que c'est une grammaire non ambiguë, beaucoup plus facile à mettre en œuvre et beaucoup plus facile à obtenir.
la source
e1 op1 e2 op2 e3 op3 ...
équivaut à,e1 op e2 and e2 op2 e3 and ...
sauf que chaque expression n’est évaluée qu’une seule fois. (BTW cette règle simple a pour effet secondaire déroutant que des déclarations commea == b is True
n'ont plus l'effetre:answer
C’est là que j’ai immédiatement réfléchi pour commenter la question principale. Je ne considère pas le support pourx < y < z
ajouter une valeur spécifique à la sémantique de la langue.(x < y) && (y < z)
est plus largement pris en charge, est plus explicite, plus expressif, plus facilement digéré en ses constituants, plus composable, plus logique, plus facilement refactorisé.La plupart des langues traditionnelles sont (au moins partiellement) orientées objet. Fondamentalement, le principe sous-jacent à OO est que les objets envoient des messages à d'autres objets (ou eux-mêmes), et le destinataire de ce message a le contrôle complet sur la manière de répondre à ce message.
Voyons maintenant comment nous pourrions mettre en œuvre quelque chose comme
On pourrait l'évaluer strictement de gauche à droite (associative de gauche):
Mais maintenant, nous appelons
__lt__
le résultat dea.__lt__(b)
, qui est unBoolean
. Ça n'a aucun sens.Essayons droit-associatif:
Non, ça n'a pas de sens non plus. Maintenant, nous avons
a < (something that's a Boolean)
.Ok, pourquoi ne pas le traiter comme un sucre syntaxique? Faisons une chaîne de n
<
comparaisons envoyer un message n-1-aire. Cela pourrait signifier, nous envoyons le message__lt__
àa
, en passantb
etc
comme arguments:Ok, ça marche, mais il y a une étrange asymétrie ici: il
a
faut décider si c'est moins queb
. Maisb
ne décide pas s’il est inférieur àc
, mais cette décision est également prise para
.Qu'en est-il de l'interpréter comme un message n-aire envoyé à
this
?Finalement! Cela peut marcher Cela signifie toutefois que l'ordre des objets n'est plus une propriété de l'objet (par exemple, si elle
a
est inférieure à ce quib
n'est pas une propriété dea
ou deb
), mais plutôt une propriété du contexte (c'est-à-direthis
).D'un point de vue général, cela semble étrange. Cependant, par exemple à Haskell, c'est normal. Il peut y avoir plusieurs implémentations différentes de la
Ord
classe de types, par exemple, et le fait de savoir si la valeura
est inférieure àb
dépend de l'instance de la classe de types qui se trouve dans la portée.Mais en réalité, ce n'est pas si étrange du tout! Java (
Comparator
) et .NET (IComparer
) ont des interfaces qui vous permettent d’injecter votre propre relation de commande dans des algorithmes de tri, par exemple. Ainsi, ils reconnaissent pleinement qu'un ordre n'est pas quelque chose qui est fixé à un type mais dépend du contexte.Autant que je sache, il n'y a actuellement aucune langue qui effectue une telle traduction. Il existe toutefois un précédent: Ioke et Seph ont tous deux ce que leur concepteur appelle des "opérateurs trinaires" - des opérateurs syntaxiquement binaires, mais sémantiquement ternaires. En particulier,
n'est pas interprété comme envoyant le message
=
àa
passerb
comme argument, mais plutôt comme envoyant le message=
au "sol actuel" (un concept similaire mais pas identique àthis
) passanta
etb
comme arguments. Donc,a = b
est interprété commeet pas
Cela pourrait facilement être généralisé aux opérateurs n-aires.
Notez que ceci est vraiment particulier aux langages OO. Dans OO, nous avons toujours un seul objet qui est en dernier ressort responsable de l'interprétation d'un message envoyé et, comme nous l'avons vu, il n'est pas immédiatement évident de déterminer
a < b < c
quel objet doit être.Cela ne s'applique cependant pas aux langages procéduraux ou fonctionnels. Par exemple, dans Scheme , Common Lisp et Clojure , la
<
fonction est n-aire et peut être appelée avec un nombre arbitraire d'arguments.En particulier,
<
ne signifie pas "inférieur à", mais ces fonctions sont interprétées légèrement différemment:la source
C'est simplement parce que les concepteurs de langage n'y ont pas pensé ou n'ont pas pensé que c'était une bonne idée. Python le fait comme vous l'avez décrit avec une grammaire simple (presque) LL (1).
la source
1 < 2 < 3
en Java ou en C # et vous n’avez pas de problème avec la priorité des opérateurs; vous avez un problème avec les types non valides. Le problème est que cela va toujours analyser exactement comme vous l'avez écrit, mais vous avez besoin d'une logique de casse spéciale dans le compilateur pour passer d'une séquence de comparaisons individuelles à une comparaison chaînée.Le programme C ++ suivant compile avec nary un aperçu de clang, même avec des avertissements définis au niveau le plus élevé possible (
-Weverything
):La suite du compilateur gnu, par contre, me le dit gentiment
comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
.La réponse est simple: compatibilité ascendante. Il y a une grande quantité de code dans la nature qui utilise l'équivalent de
1<3<2
et s'attend à ce que le résultat soit vrai.Un concepteur de langage n'a qu'une seule chance d'obtenir ce "bon", et c'est à ce moment précis que le langage est conçu pour la première fois. Le faire "mal" signifie initialement que les autres programmeurs vont rapidement tirer parti de ce comportement "mauvais". Le fait de "bien" la deuxième fois rompra cette base de code existante.
la source
if x == y is True : ...
Mon seul problème en Python est , à mon avis: les personnes qui écrivent ce type de code méritent d’être soumises à une forme de torture extra-spéciale et extraordinaire qui (s’il était en vie maintenant) provoquerait l’évanouissement de Torquemada.