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

141

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

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 surprends toujours à écrire quelque chose comme

a = !a;

ce qui devient ennuyeux quand aest une longue expression comme.

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

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

Je me demandais donc s'il existe un langage avec un opérateur de bascule booléen unaire qui me permettrait d'abréger a = !asans répéter l'expression pour a, par exemple

!=a;  
// or
a!!;

Supposons que notre langage ait un type booléen approprié (comme boolen C ++) et qu'il asoit de ce type (donc pas de style C 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 lorsqu'il boolest devenu un type intégré et si oui, pourquoi ils ont décidé de ne pas le faire.


(Note: Je sais que certaines personnes sont d'avis que la cession ne devrait pas utiliser =et que ++et +=ne sont pas opérateurs utiles , mais des défauts de conception, Assumons que je suis heureux avec eux et se concentrer sur la raison pour laquelle ils ne seraient pas étendre à bools).

CompuChip
la source
31
Qu'en est-il d'une fonction void Flip(bool& Flag) { Flag=!Flag; }Le raccourcit votre expression longue.
harper
72
this.dataSource.trackedObject.currentValue.booleanFlag ^= 1;
KamilCuk
13
des expressions longues peuvent être attribuées à des références pour simplifier le code
Quentin 2
6
@KamilCuk Cela peut fonctionner, mais vous mélangez les types. Vous affectez un entier à un booléen.
harper
7
@ user463035818 il y en a *= -1cependant, ce qui, pour une raison quelconque, je trouve plus intuitif que ^= true.
CompuChip

Réponses:

15

Cette question est en effet intéressante d'un point de vue purement théorique. Mettant de côté la question de savoir si un opérateur de bascule booléen unaire et mutant serait utile, ou pourquoi de nombreuses langues ont choisi de ne pas en fournir, je me suis aventuré dans une quête pour voir s'il existe ou non.

TL; DR apparemment non, mais Swift vous permet d'en implémenter un. Si vous souhaitez seulement voir comment cela se fait, vous pouvez faire défiler vers le bas de cette réponse.


Après une recherche (rapide) sur les fonctionnalités de différentes langues, je me sentirais en sécurité pour dire qu'aucune langue n'a implémenté cet opérateur en tant qu'opération sur place à 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 créer un. Cela nécessiterait deux choses:

  1. être capable d'implémenter des opérateurs (unaires) avec des fonctions
  2. permettre auxdites fonctions d'avoir des arguments de passage par référence (afin qu'elles puissent muter leurs arguments directement)

De nombreuses langues seront immédiatement exclues pour ne pas prendre en charge l'une ou l'autre de ces exigences ou les deux. Java for one n'autorise pas la surcharge d'opérateurs (ou d'opérateurs personnalisés) et de plus, tous les types primitifs sont passés par valeur. Go n'a aucun support pour la surcharge des opérateurs (sauf par des hacks ). Rust autorise uniquement la surcharge des opérateurs pour les types personnalisés. Vous pourriez presque y parvenir dans Scala , qui vous permet d'utiliser des fonctions nommées de manière très créative et d'omettre également les parenthèses, mais malheureusement, il n'y a pas de référence. Fortran est très proche en ce sens qu'il permet aux opérateurs personnalisés, mais leur interdit spécifiquement d'avoir des inout paramètres (qui sont autorisés dans les fonctions et sous-programmes normaux).


Il existe cependant au moins une langue qui coche toutes les cases nécessaires: Swift . Alors que certaines personnes se sont liées à la prochaine fonction membre .toggle () , vous pouvez également écrire votre propre opérateur, qui prend en effet 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
Lauri Piispanen
la source
3
Oui, mais la question demandait spécifiquement un opérateur , pas une fonction membre.
Lauri Piispanen
4
"Neither does Rust" => Oui, il le fait
Boiethios
3
@Boiethios merci! Édité pour Rust - n'autorise que les opérateurs surchargés pour les types personnalisés, donc pas de dés.
Lauri Piispanen
3
@LauriPiispanen La règle est plus générale que cela, et en raison de la règle orpheline , FYI
Boiethios
143

Basculer le bit booléen

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

Cette approche n'est pas vraiment un pur opérateur de «retournement mutant», mais remplit vos critères ci-dessus; le côté droit de l'expression n'implique pas la variable elle-même.

Tout langage avec une affectation XOR booléenne (par exemple ^=) permettrait de basculer la valeur actuelle d'une variable, par exemple a, au moyen d'une affectation XOR à 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 indiqué par @cmaster dans les commentaires ci-dessous, ce qui précède suppose qu'il aest de type bool, et non par exemple un entier ou un pointeur. S'il as'agit en fait de quelque chose d'autre (par exemple quelque chose qui booln'évalue pas à une valeur "véridique" ou "falsifiée", avec une représentation binaire qui n'est pas 0b1ou 0b0, respectivement), ce qui précède ne tient pas.

Pour un exemple concret, Java est un langage dans lequel il est bien défini et ne fait l'objet d'aucune conversion silencieuse. Citant le commentaire de @ Boann ci-dessous:

En Java, ^et ^=ont un comportement explicitement défini pour booléens et pour les entiers ( 15.22.2. Opérateurs logiques booléens &, ^et| ), où soit les deux côtés de l'opérateur doivent être booléens, ou les deux parties doivent être des nombres entiers. Il n'y a pas de conversion silencieuse entre ces types. Donc, il ne fonctionnera pas silencieusement si aest déclaré sous forme d'entier, mais donnera plutôt une erreur de compilation. C'est donc a ^= true;sûr et bien défini en Java.


Rapide: toggle()

Depuis Swift 4.2, la proposition d'évolution suivante a été acceptée et mise en œuvre:

Cela ajoute une toggle()fonction native au Booltype dans Swift.

toggle()

Active / désactive la valeur de la variable booléenne.

Déclaration

mutating func toggle()

Discussion

Utilisez cette méthode pour faire basculer une valeur booléenne de trueà falseou 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 du langage pour le basculement booléen.

dfri
la source
3
Les commentaires ne sont pas destinés à une discussion approfondie; cette conversation a été déplacée vers le chat .
Samuel Liew
44

En C ++, il est possible de commettre le péché cardinal de redéfinir le sens des opérateurs. Dans cet esprit, et un peu d'ADL, tout ce que nous devons faire pour déchaîner le chaos sur notre base d'utilisateurs est la suivante:

#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
Richard Hodges
la source
9
«Le péché cardinal de redéfinir le sens des opérateurs» - je comprends que vous avez exagéré mais ce n'est vraiment pas un péché cardinal de réaffecter de nouvelles significations aux opérateurs existants en général.
Konrad Rudolph
5
@KonradRudolph Ce que je veux dire par là, bien sûr, qu'un opérateur doit avoir un sens logique par rapport à sa signification arithmétique ou logique habituelle. 'operator +' pour 2 chaînes est logique, car la concaténation est similaire (dans notre esprit) à l'addition ou à l'accumulation. operator<<a fini par signifier «diffusé en streaming» en raison de son abus initial dans la STL. Cela ne signifiera pas naturellement «appliquer la fonction de transformation sur le RHS», ce qui est essentiellement ce à quoi je l'ai réaffecté ici.
Richard Hodges
6
Je ne pense pas que ce soit un bon argument, désolé. Tout d'abord, la concaténation de chaînes a une sémantique complètement différente de l'addition (elle n'est même pas commutative!). Deuxièmement, selon votre logique, il <<y a un opérateur de décalage de bits, pas un opérateur de «flux sortant». Le problème avec votre redéfinition n'est pas qu'elle est incompatible avec les significations existantes de l'opérateur, mais plutôt qu'elle n'ajoute aucune valeur sur un simple appel de fonction.
Konrad Rudolph
8
<<semble être une mauvaise surcharge. Je ferais !invert= x;;)
Yakk - Adam Nevraumont
12
@ Yakk-AdamNevraumont LOL, maintenant vous m'avez commencé: godbolt.org/z/KIkesp
Richard Hodges
37

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

FORTH

INVERT pour un complément au niveau du bit.

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

DrSheldon
la source
4
On peut soutenir, dans chaque langage orienté pile, un unaire notest un opérateur "bascule" :-)
Bergi
2
Bien sûr, c'est un peu une réponse de côté car l'OP pense clairement aux langages de type C qui ont une déclaration d'affectation traditionnelle. Je pense que des réponses latérales comme celle-ci ont de la valeur, ne serait-ce que pour montrer une partie du monde de la programmation à laquelle le lecteur n'aurait peut-être pas pensé. ("Il y a plus de langages de programmation dans le ciel et la terre, Horatio, que nous n'en rêvons dans notre philosophie.")
Wayne Conrad
31

La décrémentation d'un C99 boolaura l'effet désiré, tout comme l'incrémentation ou la décrémentation dubit types pris en charge dans certains dialectes de minuscules microcontrôleurs (qui, d'après ce que j'ai observé, traitent les bits comme des champs de bits larges d'un seul bit, de sorte que tous les nombres pairs sont tronqués à 0 et tous nombres impairs à 1). Je ne recommanderais pas particulièrement une telle utilisation, en partie parce que je ne suis pas un grand fan de la boolsémantique de type [À mon humble avis, le type aurait dû spécifier que a boolauquel toute valeur autre que 0 ou 1 est stockée peut se comporter lorsqu'il est lu comme si il contient une valeur entière non spécifiée (pas nécessairement cohérente); si un programme essaie de stocker une valeur entière qui n'est pas connue pour être 0 ou 1, il doit l'utiliser !!en premier].

supercat
la source
2
C ++ a fait la même chose avec bool jusqu'à C ++ 17 .
Davis Herring
31

Langue d'assemblage

NOT eax

Voir https://www.tutorialspoint.com/assembly_programming/assembly_logical_instructions.htm

Adrian
la source
2
Je ne pense pas que ce soit tout à fait ce que op avait à l'esprit, en supposant qu'il s'agit d'un assemblage x86. par exemple en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA
8
Vous pouvez utiliser tous les bits à la place: PAS 0x00000000 = 0xFFFFFFFF
Adrian
5
Eh bien, oui, même si j'essayais de faire la distinction entre les opérateurs booléens et binaires. Comme op dit dans la question, supposons que le langage a un type booléen bien défini: "(donc pas de style C int a = TRUE)."
BurnsBA
5
@BurnsBA: en effet, les langages d'assemblage n'ont pas un seul ensemble de valeurs de vérité / fausseté bien définies. À propos du code-golf, on a beaucoup discuté des valeurs que vous êtes autorisé à renvoyer pour des problèmes booléens dans différentes langues. Comme asm n'a pas de if(x)construction , il est tout à fait raisonnable d'autoriser 0 / -1 comme faux / vrai, comme pour les comparaisons SIMD ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ). Mais vous avez raison de dire que cela casse si vous l'utilisez sur une autre valeur.
Peter Cordes
4
Il est difficile d'avoir une seule représentation booléenne étant donné que PCMPEQW donne 0 / -1, mais SETcc donne 0 / + 1.
Eugene Styer
28

Je suppose que vous n'allez pas choisir 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 par 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);
}

Cela produit, comme prévu:

false
true
false
paxdiablo
la source
2
Voudriez-vous return b = !baussi? Quelqu'un qui lit foo = makenot(x) || you simplement un simple foo = makenot(bar)peut supposer que makenotc'est une fonction pure et supposer qu'il n'y a pas d'effet secondaire. Enterrer un effet secondaire dans une expression plus large est probablement un mauvais style, et le seul avantage d'un type de retour non vide est de l'activer.
Peter Cordes
2
@MatteoItalia suggère template<typename T> T& invert(T& t) { t = !t; return t; } , quel modèle sera converti vers / depuis bools'il est utilisé sur un non- boolobjet. IDK si c'est bon ou mauvais. Vraisemblablement bon.
Peter Cordes
21

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

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

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

Et puis appelez-le comme ceci:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Ainsi, votre exemple original ressemblerait à quelque chose comme:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip
Bleu Reginald
la source
2
Bien que cela fonctionne comme le raccourci recherché par l'auteur, bien sûr, vous devez utiliser deux opérateurs pour implémenter Flip(): =et Not. Il est intéressant d'apprendre que dans le cas de l'appel, SomeInstance.SomeBoolean.Flipcela fonctionne même s'il SomeBooleans'agit d'une propriété, alors que le code C # équivalent ne se compilerait pas.
BACON
14

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

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 ^= truecomme suggéré, et dans le cas de Rust, il n'y a pas de problème possible car ce falsen'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);
}
Boiethios
la source
6

En Python

Python prend en charge cette fonctionnalité, si la variable a un type booléen (qui est True ou False) avec l' exclusive or (^=)opérateur:

a = False
a ^= True
print(a)  # --> True
a ^= True
print(a)  # --> False
Andriy Ivaneyko
la source
3

En C # :

boolean.variable.down.here ^= true;

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

rakensi
la source