Pourquoi une langue n’utilise PAS l’évaluation de court-circuit?

Pourquoi une langue n’utilise PAS l’ évaluation de court-circuit ? Y a-t-il des avantages à ne pas l’utiliser?

Je vois que cela pourrait conduire à des problèmes de performances … est-ce vrai? Pourquoi?


Question connexe: Avantages de l’utilisation de l’évaluation des courts-circuits

Raisons pour ne pas utiliser l’évaluation de court-circuit:

  1. Parce qu’il se comportera différemment et produira des résultats différents si vos fonctions, vos propriétés ou vos méthodes d’opérateur ont des effets secondaires. Et cela peut entrer en conflit avec: A) les normes linguistiques, B) les versions précédentes de votre langue, ou C) les hypothèses par défaut de vos utilisateurs standard de langues. Ce sont les raisons pour lesquelles VB ne court-circuite pas.

  2. Parce que vous voudrez peut-être que le compilateur ait la liberté de réorganiser et d’élaguer les expressions, les opérateurs et les sous-expressions comme il l’entend, plutôt que dans l’ordre dans lequel l’utilisateur les a saisis. (ou du moins pas comme le feraient la plupart des développeurs de SQL). Ainsi, SQL (et quelques autres langages) peut court-circuiter, mais seulement s’il le décide et pas nécessairement dans l’ordre que vous avez implicitement spécifié.

Je suppose ici que vous posez des questions sur “la mise en court-circuit automatique, implicite, spécifique à un ordre”, ce que la plupart des développeurs attendent de C, C ++, C #, Java, etc. court-circuit. Cependant, généralement, lorsque les gens posent cette question, c’est une question “Faites ce que je veux”. c’est-à-dire qu’ils veulent dire “pourquoi ne fait-il pas ce que je veux?”, comme dans, court-circuitent automatiquement dans l’ordre que je l’ai écrit.

Un avantage auquel je peux penser est que certaines opérations peuvent avoir des effets secondaires que vous pouvez vous attendre à voir se produire.

Exemple:

 if (true || someBooleanFunctionWithSideEffect()) { ... } 

Mais c’est généralement mal vu.

Ada ne le fait pas par défaut. Pour forcer une évaluation de court-circuit, vous devez utiliser and then ou or else plutôt au lieu de and ou or .

Le problème est qu’il y a certaines circonstances où cela ralentit les choses. Si la seconde condition est rapide à calculer et que la première condition est presque toujours vraie pour “et” ou faux pour “ou”, alors l’instruction de twig supplémentaire est une sorte de perte. Cependant, je comprends qu’avec les processeurs modernes avec des prédicteurs de twig, ce n’est pas vraiment le cas. Un autre problème est que le compilateur peut savoir que la seconde moitié est moins chère ou susceptible de tomber en panne, et peut vouloir réorganiser la vérification en conséquence (ce qui ne pourrait pas être le cas si le comportement de court-circuit est défini).

J’ai entendu des objections selon lesquelles cela pourrait entraîner un comportement inattendu du code dans le cas où le deuxième test aurait des effets secondaires. IMHO il est seulement “inattendu” si vous ne connaissez pas très bien votre langue, mais certains vont discuter cela.

Si vous êtes intéressé par ce que les concepteurs de langues ont à dire sur ce problème, voici un extrait de la justification d’Ada 83 :

Les opérandes d’une expression booléenne telle que A et B peuvent être évalués dans n’importe quel ordre. En fonction de la complexité du terme B, il peut être plus efficace (sur certaines machines mais pas sur toutes) d’évaluer B uniquement lorsque le terme A a la valeur TRUE. Ceci est cependant une décision d’optimisation prise par le compilateur et il serait incorrect de supposer que cette optimisation est toujours effectuée. Dans d’autres situations, nous pouvons vouloir exprimer une conjonction de conditions où chaque condition doit être évaluée (a un sens) seulement si la condition précédente est satisfaite. Ces deux choses peuvent être faites avec des formulaires de contrôle de court-circuit …

Dans Algol 60, on peut obtenir l’effet de l’évaluation des courts-circuits uniquement en utilisant des expressions conditionnelles, car une évaluation complète est effectuée autrement. Cela conduit souvent à des constructions fastidieuses à suivre …

Plusieurs langages ne définissent pas comment les conditions booléennes doivent être évaluées. En conséquence, les programmes basés sur une évaluation de court-circuit ne seront pas portables. Cela illustre clairement la nécessité de séparer les opérateurs booléens des formulaires de contrôle des courts-circuits.

Regardez mon exemple sur le court-circuit de l’opérateur booléen On SQL Server qui montre pourquoi un certain chemin d’access dans SQL est plus efficace si le court-circuit booléen n’est pas utilisé. Mon exemple de blog montre comment le fait d’utiliser un court-circuit booléen peut casser votre code si vous supposez un court-circuit dans SQL, mais si vous lisez le raisonnement, pourquoi le SQL évalue-t-il d’abord le côté droit? Cela se traduit par un chemin d’access beaucoup plus efficace.

Bill a fait allusion à une raison valable pour ne pas utiliser les courts-circuits, mais pour l’écrire plus en détail: les architectures hautement parallèles ont parfois des problèmes avec les chemins de contrôle de twigment.

Prenez par exemple l’architecture CUDA de NVIDIA. Les puces graphiques utilisent une architecture SIMT, ce qui signifie que le même code est exécuté sur de nombreux threads parallèles. Cependant, cela ne fonctionne que si tous les threads prennent la même twig conditionnelle à chaque fois. Si différents threads prennent des chemins de code différents, l’évaluation est sérialisée – ce qui signifie que l’avantage de la parallélisation est perdu, car certains threads doivent attendre pendant que d’autres exécutent la twig de code alternatif.

La mise en court-circuit implique en fait de twigr le code, de sorte que les opérations de court-circuit peuvent nuire aux architectures SIMT telles que CUDA.

– Mais comme l’a dit Bill, c’est une question de matériel. En ce qui concerne les langues , je répondrais à votre question avec un non retentissant: éviter les courts-circuits n’a pas de sens.

Je dirais que 99 fois sur 100, je préférerais les opérateurs en court-circuit pour la performance.

Mais il y a deux grandes raisons pour lesquelles je ne les utilise pas. (Au fait, mes exemples sont en C où && et || sont en court-circuit et & et | ne le sont pas.)

1.) Lorsque vous souhaitez appeler deux fonctions ou plus dans une instruction if, quelle que soit la valeur renvoyée par la première.

 if (isABC() || isXYZ()) // short-circuiting logical operator //do stuff; 

Dans ce cas, isXYZ () est uniquement appelé si isABC () renvoie false. Mais vous pouvez vouloir que isXYZ () soit appelé quoi qu’il arrive.

Donc, à la place, vous faites ceci:

 if (isABC() | isXYZ()) // non-short-circuiting bitwise operator //do stuff; 

2.) Lorsque vous effectuez des calculs booléens avec des nombres entiers.

 myNumber = i && 8; // short-circuiting logical operator 

n’est pas nécessairement le même que:

 myNumber = i & 8; // non-short-circuiting bitwise operator 

Dans cette situation, vous pouvez réellement obtenir des résultats différents car l’opérateur en court-circuit n’évaluera pas nécessairement toute l’expression. Et cela le rend fondamentalement inutile pour les maths booléens. Donc, dans ce cas, j’utiliserais plutôt les opérateurs sans court-circuit.

Comme je le pensais, ces deux scénarios sont vraiment rares pour moi. Mais vous pouvez voir qu’il existe de réelles raisons de programmation pour les deux types d’opérateurs. Et heureusement, la plupart des langues populaires d’aujourd’hui ont les deux. Même VB.NET a les opérateurs de court-circuit AndAlso et OrElse. Si une langue aujourd’hui n’a pas les deux, je dirais qu’elle est en retard et limite vraiment le programmeur.

Si vous voulez que le côté droit soit évalué:

 if( x < 13 | ++y > 10 ) printf("do something\n"); 

Peut-être voulez-vous que l’on augmente ou non x <13. Un bon argument contre cela, cependant, est que créer des conditions sans effets secondaires est généralement une meilleure pratique de programmation.

Comme un tronçon:

Si vous vouliez une langue extrêmement sécurisée (au désortingment de la génialité), vous devriez supprimer l’évaluation des courts-circuits. Lorsque quelque chose de «sécurisé» prend un temps variable, une attaque de synchronisation peut être utilisée pour y remédier. L’évaluation du court-circuit entraîne des temps d’exécution différents, provoquant ainsi l’attaque. Dans ce cas, ne pas autoriser l’évaluation des courts-circuits permettrait d’écrire des algorithmes plus sûrs (de toute façon, des attaques de synchronisation).

Le langage de programmation Ada supportait les deux opérateurs booléens qui ne court-circuitaient pas ( AND , OR ), pour permettre à un compilateur d’optimiser et éventuellement de paralléliser les constructions, et les opérateurs avec demande explicite de court-circuit ( AND THEN , OR ELSE ) programmeur désirs. L’inconvénient d’une telle approche à deux volets est de rendre le langage un peu plus complexe (1000 décisions de conception sockets dans la même veine “faisons les deux!” Rendront globalement un langage de programmation beaucoup plus complexe ;-).

Non pas que je pense que c’est ce qui se passe actuellement dans toutes les langues, mais il serait plutôt intéressant d’alimenter les deux côtés d’une opération sur différents threads. La plupart des opérandes pourraient être prédéterminés pour ne pas interférer les uns avec les autres, donc ils seraient de bons candidats pour passer à différents processeurs.

Ce qui compte, ce sont les processeurs hautement parallèles qui ont tendance à évaluer plusieurs twigs et à en choisir une.

Hey, c’est un peu exagéré mais vous avez demandé “Pourquoi une langue” … pas “Pourquoi une langue”.

Le langage Lustre n’utilise pas l’évaluation de court-circuit. Dans if-then-elses, les deux twigs then et else sont évaluées à chaque tick, et on considère que le résultat du conditionnel dépend de l’évaluation de la condition.

La raison en est que ce langage et d’autres langages de stream de données synchrones ont une syntaxe concise pour parler du passé. Chaque twig doit être calculée pour que le passé de chacune soit disponible si cela devient nécessaire dans les cycles futurs. Le langage est censé être fonctionnel, donc ça ne compte pas, mais vous pouvez appeler les fonctions C (et peut-être remarquer qu’elles sont appelées plus souvent que vous ne le pensiez).

En lustre, écrire l’équivalent de

if (y <> 0) then 100/y else 100

est une erreur typique de débutant. La division par zéro n’est pas évitée, car l’expression 100 / y est évaluée même sur des cycles lorsque y = 0.

Parce que le court-circuit peut modifier le comportement d’une application IE:

 if(!SomeMethodThatChangesState() || !SomeOtherMethodThatChangesState()) 

Je dirais que c’est valable pour les problèmes de lisibilité; Si quelqu’un profite de l’évaluation de court-circuit d’une manière qui n’est pas évidente, il peut être difficile pour un responsable d’examiner le même code et de comprendre la logique.

Si la mémoire sert, erlang fournit deux constructions, standard et / ou, puis andalso / orelse. Cela clarifie l’intention que «oui, je sais que c’est un court-circuit, et vous devriez aussi», où, comme à d’autres moments, l’intention doit être déduite du code.

À titre d’exemple, disons qu’un responsable rencontre ces lignes:

 if(user.inDatabase() || user.insertInDatabase()) user.DoCoolStuff(); 

Il faut quelques secondes pour reconnaître que l’intention est “si l’utilisateur n’est pas dans la firebase database, insérez-le (s), si cela fonctionne bien”.

Comme d’autres l’ont souligné, ce n’est vraiment pertinent que lorsque l’on fait des choses avec des effets secondaires.

Je ne connais aucun problème de performance, mais une des arguments possibles pour l’éviter (ou du moins une utilisation excessive) est que cela peut créer la confusion chez les autres développeurs.

Il y a déjà de bonnes réponses sur la question des effets secondaires, mais je n’ai rien vu sur l’aspect performance de la question.

Si vous n’autorisez pas l’évaluation de court-circuit, le problème de performance est que les deux côtés doivent être évalués même si cela ne changera pas le résultat. Ceci est généralement un non-problème, mais peut devenir pertinent dans l’une de ces deux circonstances:

  • Le code est dans une boucle interne appelée très fréquemment
  • Il y a un coût élevé associé à l’évaluation des expressions (peut-être IO ou un calcul coûteux)

L’évaluation du court-circuit fournit automatiquement une évaluation conditionnelle d’une partie de l’expression.

Le principal avantage est que cela simplifie l’expression.

La performance pourrait être améliorée mais vous pourriez également observer une pénalité pour des expressions très simples.

Une autre conséquence est que les effets secondaires de l’évaluation de l’expression pourraient être affectés.

En général, utiliser des effets secondaires n’est pas une bonne pratique, mais dans certains contextes spécifiques, cela pourrait être la solution préférée.

VB6 n’utilise pas l’évaluation des courts-circuits, je ne sais pas si les nouvelles versions le font, mais j’en doute. Je crois que c’est simplement parce que les anciennes versions ne le faisaient pas non plus, et parce que la plupart des personnes qui utilisaient VB6 ne s’attendaient pas à ce que cela se produise, ce qui créerait de la confusion.

C’est l’une des choses qui m’a rendu extrêmement difficile de devenir un programmeur VOB noob qui a écrit du code spaghetti et de poursuivre mon parcours pour devenir un vrai programmeur.

Beaucoup de réponses ont parlé d’effets secondaires. Voici un exemple Python sans effets secondaires dans lequel (à mon avis) le court-circuit améliore la lisibilité.

 for i in range(len(myarray)): if myarray[i]>5 or (i>0 and myarray[i-1]>5): print "At index",i,"either arr[i] or arr[i-1] is big" 

Le court-circuit garantit que nous n’essayons pas d’accéder à myarray [-1], ce qui provoquerait une exception puisque les tableaux Python commencent à 0. Le code pourrait bien sûr être écrit sans court-circuit, par exemple

 for i in range(len(myarray)): if myarray[i]<=5: continue if i==0: continue if myarray[i-1]<=5: continue print "At index",i,... 

mais je pense que la version de court-circuit est plus lisible.