Qualche lingua ha un operatore toggle booleano unario?


141

Quindi questa è più una domanda teorica. C ++ e linguaggi (in) direttamente basati su di esso (Java, C #, PHP) hanno operatori di scelta rapida per assegnare il risultato della maggior parte degli operatori binari al primo operando, come

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

ma quando voglio attivare un'espressione booleana mi trovo sempre a scrivere qualcosa del genere

a = !a;

che diventa fastidioso quando aè una lunga espressione come.

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

(sì, la legge di Demetra, lo so).

Quindi mi chiedevo, c'è qualche lingua con un operatore toggle booleano unario che mi permetterebbe di abbreviare a = !asenza ripetere l'espressione per a, ad esempio

!=a;  
// or
a!!;

Supponiamo che il nostro linguaggio abbia un tipo booleano appropriato (come boolin C ++) e che asia di quel tipo (quindi non in stile C int a = TRUE).

Se riesci a trovare una fonte documentata, sarei anche interessato a sapere se, ad esempio, i progettisti di C ++ hanno preso in considerazione l'aggiunta di un operatore del genere quando è booldiventato un tipo incorporato e, in tal caso, perché hanno deciso di non farlo.


(Nota: So che alcune persone sono del parere che l'assegnazione non deve usare =e che ++e +=non sono utili, ma gli operatori difetti di progettazione; diciamo solo supporre sono felice con loro e concentrarsi sul perché essi non estendere a Caccio).


31
Che dire di una funzione void Flip(bool& Flag) { Flag=!Flag; }Accorcia la tua lunga espressione.
Harper il

72
this.dataSource.trackedObject.currentValue.booleanFlag ^= 1;
KamilCuk,

13
espressioni lunghe possono essere assegnate a riferimenti per semplificare il codice
Quentin 2

6
@KamilCuk Potrebbe funzionare, ma i tipi di confusione. Si assegna un numero intero a un bool.
Harper il

7
@ user463035818 c'è *= -1però, che per qualche motivo trovo più intuitivo di ^= true.
CompuChip,

Risposte:


15

Questa domanda è davvero interessante da un punto di vista puramente teorico. Mettendo da parte se un operatore di commutazione booleana unario e mutevole sarebbe utile o perché molte lingue abbiano deciso di non fornirne uno, mi sono avventurato in una ricerca per vedere se effettivamente esiste.

TL; DR apparentemente no, ma Swift ti consente di implementarne uno. Se desideri solo vedere come è fatto, puoi scorrere fino alla fine di questa risposta.


Dopo una (rapida) ricerca di funzionalità di varie lingue, mi sento sicuro di dire che nessuna lingua ha implementato questo operatore come una rigorosa operazione di muting sul posto (correggimi se ne trovi una). Quindi la prossima cosa sarebbe vedere se ci sono lingue che ti permettono di costruirne una. Ciò che richiederebbe è due cose:

  1. essere in grado di implementare operatori (unari) con funzioni
  2. consentire a dette funzioni di avere argomenti pass-by-reference (in modo che possano mutare direttamente i loro argomenti)

Molte lingue verranno immediatamente escluse per non supportare uno o entrambi questi requisiti. Java per uno non consente il sovraccarico dell'operatore (o operatori personalizzati) e inoltre, tutti i tipi primitivi vengono passati per valore. Go non ha alcun supporto per il sovraccarico dell'operatore (tranne che per gli hack ). La ruggine consente il sovraccarico dell'operatore solo per tipi personalizzati. Potresti quasi raggiungere questo obiettivo in Scala , che ti consente di utilizzare funzioni con un nome molto creativo e di omettere le parentesi, ma purtroppo non c'è riferimento pass-by. Fortran si avvicina molto al fatto che consente agli operatori personalizzati, ma specificamente proibisce loro di entrare parametri (consentiti nelle normali funzioni e subroutine).


Esiste tuttavia almeno una lingua che spunta tutte le caselle necessarie: Swift . Mentre alcune persone si sono collegate alla prossima funzione membro .toggle () , puoi anche scrivere il tuo operatore, che in effetti supporta argomenti inout . Ecco ed ecco:

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false


3
Sì, ma la domanda ha richiesto specificamente un operatore , non una funzione membro.
Lauri Piispanen,

4
"Né does Rust" => Sì, lo fa
Boiethios,

3
@Boiethios grazie! Modificato per Rust: consente solo agli operatori sovraccarichi di tipi personalizzati, quindi nessun dado.
Lauri Piispanen,

3
@LauriPiispanen La regola è più generale di così, e a causa della regola orfana , FYI
Boiethios

143

Attivare il bit booleano

... ciò mi permetterebbe di abbreviare a = !a senza ripetere l'espressione pera ...

Questo approccio non è in realtà un puro operatore di "muting flip", ma soddisfa i tuoi criteri sopra; il lato destro dell'espressione non coinvolge la variabile stessa.

Qualsiasi lingua con un'assegnazione XOR booleana (ad es. ^=) Consentirebbe di capovolgere il valore corrente di una variabile, diciamo a, mediante l'assegnazione XOR a true:

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

Come sottolineato da @cmaster nei commenti sottostanti, quanto sopra presuppone che asia di tipo boole non ad esempio un numero intero o un puntatore. Se ain realtà è qualcos'altro (ad esempio qualcosa che non sta boolvalutando un valore "verità" o "falsa", con una rappresentazione bit che non è 0b1o 0b0, rispettivamente), non vale quanto sopra.

Per un esempio concreto, Java è un linguaggio in cui questo è ben definito e non soggetto a conversioni silenziose. Citando il commento di @ Boann dal basso:

In Java, ^e ^=hanno esplicitamente definito il comportamento per i booleani e per gli interi ( 15.22.2. Gli operatori logici booleani &, ^e| ), in cui o entrambi i lati del operatore deve essere booleani, o entrambe le parti devono essere interi. Non esiste una conversione silenziosa tra questi tipi. Quindi non funzionerà in modo silenzioso se aviene dichiarato come intero, ma piuttosto darà un errore di compilazione. Quindi a ^= true;è sicuro e ben definito in Java.


Swift: toggle()

A partire da Swift 4.2, la seguente proposta di evoluzione è stata accettata e implementata:

Ciò aggiunge una toggle()funzione nativa al Booltipo in Swift.

toggle()

Attiva o disattiva il valore della variabile booleana.

Dichiarazione

mutating func toggle()

Discussione

Utilizzare questo metodo per alternare un valore booleano da truea falseo da falsea true.

var bools = [true, false]

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

Questo non è di per sé un operatore, ma consente un approccio madrelingua per la commutazione booleana.


3
I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Samuel Liew

44

In C ++ è possibile commettere il Cardinale Sin di ridefinire il significato degli operatori. Con questo in mente e un po 'di ADL, tutto ciò che dobbiamo fare per scatenare il caos sulla nostra base di utenti è questo:

#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;
}

uscita prevista:

6
0
1
0
1

9
"Il peccato cardinale di ridefinire il significato degli operatori" - Capisco che tu stia esagerando, ma in realtà non è un peccato cardinale riassegnare nuovi significati agli operatori esistenti in generale.
Konrad Rudolph,

5
@KonradRudolph Cosa intendo con questo, ovviamente, che un operatore dovrebbe avere un senso logico rispetto al suo solito significato aritmetico o logico. 'operatore + `per 2 stringhe è logico, poiché la concatenazione è simile (agli occhi della nostra mente) all'addizione o all'accumulazione. operator<<è venuto a significare "espulso" a causa del suo abuso originale nella STL. Non significherà naturalmente "applicare la funzione di trasformazione sull'RHS", che è essenzialmente ciò per cui l'ho riproposta qui.
Richard Hodges,

6
Non credo sia una buona discussione, scusa. Prima di tutto, la concatenazione di stringhe ha una semantica completamente diversa dall'aggiunta (non è nemmeno commutativa!). In secondo luogo, secondo la tua logica <<c'è un operatore a spostamento di bit, non un operatore "stream out". Il problema con la tua ridefinizione non è che non è coerente con i significati esistenti dell'operatore, ma piuttosto che non aggiunge alcun valore rispetto a una semplice chiamata di funzione.
Konrad Rudolph,

8
<<sembra un brutto sovraccarico. Lo farei !invert= x;;)
Yakk - Adam Nevraumont il

12
@ Yakk-AdamNevraumont LOL, ora mi hai fatto iniziare: godbolt.org/z/KIkesp
Richard Hodges

37

Finché includiamo il linguaggio assembly ...

VIA

INVERT per un complemento bit a bit.

0= per un complemento logico (vero / falso).


4
Discutibile, in ogni lingua orientata allo stack un unario notè un operatore "toggle" :-)
Bergi

2
Certo, è un po 'una risposta laterale poiché l'OP sta chiaramente pensando a linguaggi simil-C che hanno una dichiarazione di assegnazione tradizionale. Penso che risposte laterali come questa abbiano valore, se non altro per mostrare una parte del mondo della programmazione a cui il lettore potrebbe non aver pensato. ("Esistono più linguaggi di programmazione in cielo e in terra, Horatio, di quelli che si sogna nella nostra filosofia.")
Wayne Conrad,

31

Il decremento di un C99 boolavrà l'effetto desiderato, così come l'incremento o il decremento dei bittipi supportati in alcuni dialetti di microcontrollori piccoli (che da quello che ho osservato trattano i bit come bitfield wide a bit singolo, quindi tutti i numeri pari vengono troncati a 0 e tutti numeri dispari a 1). Non consiglierei in particolare tale utilizzo, in parte perché non sono un grande fan della boolsemantica del tipo [IMHO, il tipo avrebbe dovuto specificare che un boolvalore a cui è memorizzato qualsiasi valore diverso da 0 o 1 potrebbe comportarsi quando letto come se contiene un valore intero non specificato (non necessariamente coerente); se un programma sta tentando di memorizzare un valore intero che non è noto essere 0 o 1, dovrebbe usarlo !!per primo].


2
C ++ ha fatto la stessa cosa con bool fino a C ++ 17 .
Davis Herring,

31

2
Non penso che questo sia esattamente ciò che op aveva in mente, supponendo che si tratti di un assembly x86. ad es. en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA

8
Puoi invece usare tutti i bit: NOT 0x00000000 = 0xFFFFFFFF
Adrian

5
Bene, sì, anche se stavo cercando di tracciare la distinzione tra operatori booleani e bit per bit. Come dice op nella domanda, supponiamo che il linguaggio abbia un tipo booleano ben definito: "(quindi niente C-style int a = TRUE)."
BurnsBA

5
@BurnsBA: in effetti, i linguaggi di assemblaggio non hanno un unico insieme di valori di verità / falsità ben definiti. Oltre al code-golf, è stato discusso molto su quali valori è possibile restituire per problemi booleani in varie lingue. Poiché asm non ha un if(x)costrutto , è del tutto ragionevole consentire 0 / -1 come falso / vero, come per i confronti SIMD ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ). Ma hai ragione che questo si interrompe se lo usi su qualsiasi altro valore.
Peter Cordes,

4
È difficile avere un'unica rappresentazione booleana dato che PCMPEQW risulta in 0 / -1, ma SETcc risulta in 0 / + 1.
Eugene Styer,

28

Suppongo che non sceglierai un linguaggio basato esclusivamente su questo :-) In ogni caso, puoi farlo in C ++ con qualcosa del tipo:

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

Vedere ad esempio il seguente programma completo:

#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);
}

Questo produce, come previsto:

false
true
false

2
Lo vorresti anche tu return b = !b? Qualcuno che legge foo = makenot(x) || yo semplicemente un semplice foo = makenot(bar)potrebbe supporre che makenotfosse una funzione pura, e supporre che non ci fossero effetti collaterali. Seppellire un effetto collaterale all'interno di un'espressione più ampia è probabilmente un cattivo stile, e l'unico vantaggio di un tipo di ritorno non vuoto è abilitarlo.
Peter Cordes,

2
@MatteoItalia suggerisce template<typename T> T& invert(T& t) { t = !t; return t; } che il modello verrà convertito in / da boolse utilizzato su un non boologgetto. IDK se è buono o cattivo. Presumibilmente buono.
Peter Cordes,


21

Visual Basic.Net supporta questo tramite un metodo di estensione.

Definire il metodo di estensione in questo modo:

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

E poi chiamalo così:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Quindi, il tuo esempio originale sarebbe simile a:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip

2
Mentre questo funziona come la scorciatoia che l'autore stava cercando, ovviamente, è necessario utilizzare due operatori per implementare Flip(): =e Not. È interessante apprendere che nel caso di chiamata SomeInstance.SomeBoolean.Flipfunziona anche se SomeBooleanè una proprietà, mentre il codice C # equivalente non verrebbe compilato.
BACON,

14

In Rust, puoi creare il tuo tratto per estendere i tipi che implementano il Nottratto:

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);
}

Puoi anche usare ^= truecome suggerito, e nel caso di Rust, non ci sono possibili problemi per farlo perché falsenon è un numero intero "mascherato" come in C o C ++:

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

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


3

In C # :

boolean.variable.down.here ^= true;

L'operatore booleano ^ è XOR e XORing con true equivale a invertire.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.