web-dev-qa-db-fra.com

Une langue a-t-elle un opérateur de basculement booléen unaire?

Donc, c'est plus une question théorique. C++ et les langages (in) directement basés sur celui-ci (Java, C #, PHP) ont opérateurs de raccourci pour affecter le résultat de la plupart des opérateurs binaires au premier opérande, tel que

a += 3;   // for a = a + 3
a *= 3;   // for a = a * 3;
a <<= 3;  // for a = a << 3;

mais quand je veux basculer une expression booléenne, je me retrouve toujours à écrire quelque chose comme

a = !a;

ce qui devient agaçant quand a est une expression longue comme.

this.dataSource.trackedObject.currentValue.booleanFlag =
    !this.dataSource.trackedObject.currentValue.booleanFlag;

(oui, la loi de Demeter, je sais).

Je me demandais donc s'il existait un langage avec un opérateur de basculement booléen unaire qui me permettrait de raccourcir a = !a sans répéter l'expression de a, par exemple

!=a;  
// or
a!!;

Supposons que notre langage a un type booléen correct (comme bool en C++) et que a est de ce type (donc pas de C-style int a = TRUE).

Si vous pouvez trouver une source documentée, je serais également intéressé de savoir si, par exemple, les concepteurs C++ ont envisagé d'ajouter un opérateur comme celui-ci lorsque bool devenait un type intégré et, dans l'affirmative, pourquoi ils l'avaient décidé.


(Remarque: je sais que certaines personnes sont d'avis que l'affectation ne devrait pas utiliser = et que ++ et += ne sont pas des opérateurs utiles mais des défauts de conception; supposons que je suis heureux. avec eux et se concentrer sur la raison pour laquelle ils ne s’étendraient pas aux bools).

140
CompuChip

Cette question est en effet intéressante d'un point de vue purement théorique. Mettant de côté si un opérateur de basculement booléen en mutation, unaire et mutant ​​serait utile, ou pourquoi de nombreuses langues ont choisi de ne pas en fournir un, je me suis aventuré à la recherche de son existence ou non.

TL; DR apparemment non, mais Swift vous permet d'en implémenter un. Si vous souhaitez seulement voir comment cela se passe, vous pouvez aller au bas de cette réponse.


Après une recherche (rapide) sur les caractéristiques de différentes langues, je me risquerais à dire qu'aucune langue n'a implémenté cet opérateur en tant qu'opération in situ en mutation stricte (corrigez-moi si vous en trouvez une). La prochaine chose à faire serait donc de voir s’il existe des langages qui vous permettent d’en construire un. Cela nécessiterait deux choses:

  1. être capable d'implémenter des opérateurs (unaires) avec des fonctions
  2. autoriser lesdites fonctions à avoir des arguments de type référence par mot de passe (afin qu'ils puissent directement muter leurs arguments)

De nombreuses langues seront immédiatement exclues pour non-prise en charge de l'une ou l'autre de ces exigences. Java pour l'un n'autorise pas la surcharge des opérateurs (ou les opérateurs personnalisés) et, en outre, tous les types primitifs sont passés par valeur. Go ne prend pas en charge la surcharge de l'opérateur (sauf par hacks ). Rust n'autorise la surcharge de l'opérateur que pour les types personnalisés. Vous pouvez presque atteindre ceci en Scala , ce qui vous permet d'utiliser des fonctions nommées de manière très créative et d'omettre les parenthèses, mais malheureusement, il n'y a pas passer par référence. Fortran est très proche dans la mesure où il autorise les opérateurs personnalisés, mais leur interdit en particulier d'avoir des paramètres inout ​​(qui sont autorisés dans fonctions normales et sous-routines).


Il existe cependant au moins une langue qui coche toutes les cases nécessaires: Swift . Bien que certaines personnes se soient associées à la fonction membre . Toggle (), vous pouvez également écrire votre propre opérateur, qui prend en charge les arguments inout. Et voilà:

prefix operator ^

prefix func ^ (b: inout Bool) {
    b = !b
}

var foo = true
print(foo)
// true

^foo

print(foo)
// false
14
Lauri Piispanen

Basculer le bit booléen

... cela me permettrait d'abréger _a = !a_ sans répéter l'expression pour a ...

Cette approche n’est pas vraiment un pur opérateur de "basculement en mutation", mais elle répond aux critères ci-dessus; le côté droit de l'expression n'implique pas la variable elle-même.

Toute langue avec une affectation booléenne XOR (par exemple _^=_) permettrait de retourner la valeur actuelle d'une variable, par exemple a, au moyen de XOR affectation à true:

_// type of a is bool
a ^= true;  // if a was false, it is now true,
            // if a was true, it is now false
_

Comme l'a souligné @cmaster dans les commentaires ci-dessous, ce qui précède suppose que a est de type bool et non pas, par exemple. un entier ou un pointeur. Si a est en fait quelque chose d'autre (par exemple quelque chose de non -bool évaluant une valeur de "vérité" ou "fausseté", avec une représentation de bit qui n'est pas _0b1_ ou _0b0_, respectivement), le précédent pas tenir.

Par exemple, Java est un langage où il est bien défini et n'est sujet à aucune conversion en mode silencieux. Citant le commentaire de @ Boann ci-dessous:

En Java, _^_ et _^=_ ont explicitement défini le comportement des booléens et des entiers ( 15.22.2. Opérateurs logiques booléens _&_, _^_ et _|_ ), où l’un ou l’autre des deux côtés de l’opérateur doit être un booléen ou un entier. Il n'y a pas de conversion silencieuse entre ces types. Donc, cela ne fonctionnera pas en silence si a est déclaré comme un entier, mais plutôt une erreur de compilation. Donc _a ^= true;_ est sécurisé et bien défini en Java.


Rapide: toggle()

À partir de Swift4.2, la proposition d'évolution suivante a été acceptée et mise en œuvre:

Ceci ajoute une fonction toggle() native à la Bool dans Swift.

toggle()

Bascule la valeur de la variable booléenne.

Déclaration

_mutating func toggle()
_

Discussion

Utilisez cette méthode pour basculer une valeur booléenne de true à false ou de false à true.

_var bools = [true, false]

bools[0].toggle() // bools == [false, false]
_

Ce n'est pas un opérateur en soi, mais permet une approche native pour le basculement booléen.

143
dfri

En C++, il est possible de commettre le péché cardinal de redéfinir le sens des opérateurs. Dans cet esprit, et avec un peu d'ADL, tout ce que nous avons à faire pour libérer le chaos de nos utilisateurs est le suivant:

#include <iostream>

namespace notstd
{
    // define a flag type
    struct invert_flag {    };

    // make it available in all translation units at zero cost
    static constexpr auto invert = invert_flag{};

    // for any T, (T << invert) ~= (T = !T)    
    template<class T>
    constexpr T& operator<<(T& x, invert_flag)
    {
        x = !x;
        return x;
    }
}

int main()
{
    // unleash Hell
    using notstd::invert;

    int a = 6;
    std::cout << a << std::endl;

    // let confusion reign amongst our hapless maintainers    
    a << invert;
    std::cout << a << std::endl;

    a << invert;
    std::cout << a << std::endl;

    auto b = false;
    std::cout << b << std::endl;

    b << invert;
    std::cout << b << std::endl;
}

production attendue:

6
0
1
0
1
43
Richard Hodges

Tant que nous incluons le langage d'assemblage ...

FORTH

INVERT pour un complément binaire.

0= pour un complément logique (vrai/faux).

37
DrSheldon

La décrémentation d’un C99 bool aura l’effet souhaité, de même que l’incrémentation ou la décrémentation des types bit pris en charge par certains dialectes de microcontrôleur minuscule de sorte que tous les nombres pairs sont tronqués à 0 et tous les nombres impairs à 1). Je ne recommanderais pas particulièrement cet usage, en partie parce que je ne suis pas un grand fan de la sémantique du type bool [à mon humble avis, le type aurait dû spécifier un bool auquel toute valeur autre que 0 ou 1 est stocké peut se comporter lorsqu'il est lu comme s'il contenait une valeur entière non spécifiée (pas nécessairement cohérente); si un programme tente de stocker une valeur entière dont la valeur n'est pas connue comme 0 ou 1, il doit d'abord utiliser !!.

31
supercat
31

Je suppose que vous ne choisirez pas un langage basé uniquement sur ceci :-) Dans tous les cas, vous pouvez le faire en C++ avec quelque chose comme:

inline void makenot(bool &b) { b = !b; }

Voir le programme complet suivant pour exemple:

#include <iostream>

inline void makenot(bool &b) { b = !b; }

inline void outBool(bool b) { std::cout << (b ? "true" : "false") << '\n'; }

int main() {
    bool this_dataSource_trackedObject_currentValue_booleanFlag = false;
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);

    makenot(this_dataSource_trackedObject_currentValue_booleanFlag);
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);

    makenot(this_dataSource_trackedObject_currentValue_booleanFlag);
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);
}

Cette sortie, comme prévu:

false
true
false
28
paxdiablo

PostScript , étant un concaténatif , orienté pile comme Forth, a une bascule unaire, pas . L'opérateur not bascule la valeur en haut de la pile. Par exemple,

true    % Push true onto the stack
not     % invert the top of stack
        % the top of stack is now false

Voir le Manuel de référence du langage PostScript (pdf) , p. 458.

22
Wayne Conrad

Visual Basic.Net prend en charge cela via une méthode d'extension.

Définissez la méthode d'extension comme suit:

<Extension>
Public Sub Flip(ByRef someBool As Boolean)
    someBool = Not someBool
End Sub

Et puis appelez ça comme ça:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Ainsi, votre exemple original ressemblerait à quelque chose comme:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip
21
Reginald Blue

Dans Rust, vous pouvez créer votre propre trait pour étendre les types qui implémentent le trait Not:

use std::ops::Not;
use std::mem::replace;

trait Flip {
    fn flip(&mut self);
}

impl<T> Flip for T
where
    T: Not<Output = T> + Default,
{
    fn flip(&mut self) {
        *self = replace(self, Default::default()).not();
    }
}

#[test]
fn it_works() {
    let mut b = true;
    b.flip();

    assert_eq!(b, false);
}

Vous pouvez également utiliser ^= true comme suggéré et, dans le cas de Rust, il n'y a pas de problème possible, car false n'est pas un entier "déguisé" comme en C ou C++:

fn main() {
    let mut b = true;
    b ^= true;
    assert_eq!(b, false);

    let mut b = false;
    b ^= true;
    assert_eq!(b, true);
}
14
French Boiethios

en Python

Python supporte une telle fonctionnalité, si la variable a type bool (ce qui est True ou False) avec l'opérateur exclusive or (^=) :

a = False
a ^= True
print(a)  # --> True
a ^= True
print(a)  # --> False
6
Andriy Ivaneyko

En C # :

boolean.variable.down.here ^= true;

L'opérateur booléen ^ est XOR, et XORing avec true est identique à l'inversion.

3
rakensi