Hat eine Sprache einen unären booleschen Umschaltoperator?

141

Das ist also eher eine theoretische Frage. C ++ und direkt darauf basierende Sprachen (in) (Java, C #, PHP) verfügen über Verknüpfungsoperatoren , mit denen das Ergebnis der meisten Binäroperatoren dem ersten Operanden zugewiesen werden kann, z

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

Aber wenn ich einen booleschen Ausdruck umschalten möchte, schreibe ich immer so etwas

a = !a;

was nervt wenn aein langer ausdruck ist wie.

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

(Ja, Demeters Gesetz, ich weiß).

Also ich frage mich, ist es eine Sprache mit einem unären boolean Toggle Operator , der mich abzukürzen erlauben würde , a = !aohne Wiederholung der Ausdruck für a, zum Beispiel

!=a;  
// or
a!!;

Nehmen wir an, dass unsere Sprache einen richtigen booleschen Typ hat (wie boolin C ++) und dass adieser vom Typ ist (also kein C-Stil int a = TRUE).

Wenn Sie eine dokumentierte Quelle finden, würde mich auch interessieren, ob beispielsweise die C ++ - Designer erwogen haben, einen solchen Operator hinzuzufügen, als er boolzu einem integrierten Typ wurde, und wenn ja, warum sie sich dagegen entschieden haben.


(Anmerkung: Ich weiß , dass einige Leute der Meinung sind , dass Zuordnung nicht verwenden sollten , =und dass ++und +=sind nicht nützlich Betreiber aber Design - Fehler, lassen Sie uns einfach davon ausgehen , ich bin glücklich mit ihnen und konzentrieren sich auf , warum sie bools nicht verlängern würde).

CompuChip
quelle
31
Was ist mit einer Funktion? void Flip(bool& Flag) { Flag=!Flag; }Die verkürzt Ihren langen Ausdruck.
Harper
72
this.dataSource.trackedObject.currentValue.booleanFlag ^= 1;
KamilCuk
13
Referenzen können lange Ausdrücke zugewiesen werden, um den Code zu vereinfachen
Quentin 2
6
@KamilCuk Das könnte funktionieren, aber Sie verwechseln Typen. Sie weisen einem Bool eine Ganzzahl zu.
Harper
7
@ user463035818 gibt es *= -1aber, was ich aus irgendeinem Grund intuitiver finde als ^= true.
CompuChip

Antworten:

15

Diese Frage ist in der Tat aus rein theoretischer Sicht interessant. Abgesehen davon, ob ein unärer, mutierender boolescher Umschaltoperator nützlich wäre oder nicht , oder warum sich viele Sprachen dafür entschieden haben, keinen bereitzustellen, wagte ich mich auf die Suche, ob er tatsächlich existiert oder nicht.

TL; DR anscheinend nein, aber mit Swift können Sie eine implementieren. Wenn Sie nur sehen möchten, wie es gemacht wird, können Sie zum Ende dieser Antwort scrollen.


Nach einer (schnellen) Suche nach Funktionen verschiedener Sprachen kann ich mit Sicherheit sagen, dass keine Sprache diesen Operator als streng mutierende In-Place-Operation implementiert hat (korrigieren Sie mich, wenn Sie eine finden). Als nächstes sollten Sie also prüfen, ob es Sprachen gibt, mit denen Sie eine erstellen können. Was dies erfordern würde, sind zwei Dinge:

  1. in der Lage sein, (unäre) Operatoren mit Funktionen zu implementieren
  2. Ermöglichen, dass diese Funktionen Referenzargumente haben (damit sie ihre Argumente direkt mutieren können)

Viele Sprachen werden sofort ausgeschlossen, wenn sie eine oder beide dieser Anforderungen nicht unterstützen. Java erlaubt keine Überladung von Operatoren (oder benutzerdefinierte Operatoren) und außerdem werden alle primitiven Typen als Wert übergeben. Go unterstützt keinerlei Überlastung des Bedieners (außer durch Hacks ). Rust erlaubt nur das Überladen von Bedienern für benutzerdefinierte Typen. Sie könnten dies in Scala fast erreichen , wodurch Sie sehr kreativ benannte Funktionen verwenden und auch Klammern weglassen können, aber leider gibt es keine Referenzübergabe. Fortran kommt dem sehr nahe, da es benutzerdefinierte Operatoren zulässt, ihnen jedoch ausdrücklich verbietet, Inout zu haben Parameter (die in normalen Funktionen und Unterprogrammen zulässig sind).


Es gibt jedoch mindestens eine Sprache, die alle erforderlichen Kästchen ankreuzt: Swift . Während einige Leute auf die bevorstehende .toggle () -Mitgliedsfunktion verlinkt haben , können Sie auch Ihren eigenen Operator schreiben, der tatsächlich inout- Argumente unterstützt . Siehe da:

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false
Lauri Piispanen
quelle
6
Die Sprache wird dies bereitstellen: github.com/apple/swift-evolution/blob/master/proposals/…
Cristik
3
Ja, aber die Frage wurde speziell für einen Operator gestellt , nicht für eine Mitgliedsfunktion.
Lauri Piispanen
4
"Rust auch nicht" => Ja
Boiethios
3
@ Boiethios danke! Für Rost bearbeitet - erlaubt jedoch nur überladene Operatoren für benutzerdefinierte Typen, also keine Würfel.
Lauri Piispanen
3
@LauriPiispanen Die Regel ist allgemeiner als das, und aufgrund der Waisenregel , FYI
Boiethios
143

Das Boolesche Bit umschalten

... das würde mir erlauben, abzukürzen, a = !a ohne den Ausdruck füra ...

Dieser Ansatz ist nicht wirklich ein reiner "mutierender Flip" -Operator, erfüllt jedoch die oben genannten Kriterien. Die rechte Seite des Ausdrucks enthält nicht die Variable selbst.

Jede Sprache mit einer booleschen XOR-Zuweisung (z. B. ^=) würde das Umdrehen des aktuellen Werts einer Variablen ermöglichen, z. B. amittels XOR-Zuweisung an true:

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

Wie von @cmaster in den Kommentaren unten ausgeführt, wird davon ausgegangen, dass aes sich um einen Typ boolhandelt und nicht um eine Ganzzahl oder einen Zeiger. Wenn aes sich tatsächlich um etwas anderes handelt (z. B. um etwas, das nicht boolauf einen "wahrheitsgemäßen" oder "falschen" Wert bewertet wird, mit einer Bitdarstellung, die nicht 0b1bzw. 0b0ist), gilt das Obige nicht.

Für ein konkretes Beispiel ist Java eine Sprache, in der dies genau definiert ist und keine stillen Konvertierungen vorgenommen werden. Zitat von @ Boanns Kommentar von unten:

In Java ^und ^=haben explizit definiertes Verhalten für Boolesche Werte und für Ganzzahlen ( 15.22.2. Boolesche logische Operatoren &, ^und| ), wobei entweder beide Seiten des Operators Boolesche Werte oder beide Seiten Ganzzahlen sein müssen. Es gibt keine stille Konvertierung zwischen diesen Typen. Es wird also nicht stillschweigend zu Fehlfunktionen kommen, wenn aes als Ganzzahl deklariert wird, sondern es wird ein Kompilierungsfehler ausgegeben. Ist a ^= true;also sicher und gut definiert in Java.


Schnell: toggle()

Ab Swift 4.2 wurde der folgende Evolutionsvorschlag akzeptiert und umgesetzt:

Dies fügt toggle()dem BoolTyp in Swift eine native Funktion hinzu .

toggle()

Schaltet den Wert der Booleschen Variablen um.

Erklärung

mutating func toggle()

Diskussion

Verwenden Sie diese Methode, um einen Booleschen Wert von truenach falseoder von falsenach umzuschalten true.

var bools = [true, false]

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

Dies ist per se kein Operator, ermöglicht jedoch einen sprachlichen Ansatz für das boolesche Umschalten.

dfri
quelle
3
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Samuel Liew
44

In C ++ ist es möglich, die Hauptsünde der Neudefinition der Bedeutung von Operatoren zu begehen. In diesem Sinne und mit ein wenig ADL müssen wir nur Folgendes tun, um Chaos auf unserer Benutzerbasis auszulösen:

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

erwartete Ausgabe:

6
0
1
0
1
Richard Hodges
quelle
9
"Die Hauptsünde der Neudefinition der Bedeutung von Operatoren" - Ich verstehe, dass Sie übertrieben haben, aber es ist wirklich keine Hauptsünde, bestehenden Operatoren im Allgemeinen neue Bedeutungen zuzuweisen.
Konrad Rudolph
5
@KonradRudolph Was ich damit meine, ist natürlich, dass ein Operator in Bezug auf seine übliche arithmetische oder logische Bedeutung logisch sinnvoll sein sollte. 'operator + `für 2 Strings ist logisch, da die Verkettung (in unseren Augen) der Addition oder Akkumulation ähnlich ist. operator<<ist aufgrund seines ursprünglichen Missbrauchs in der STL zu "gestreamt" geworden. Es wird natürlich nicht bedeuten, die Transformationsfunktion auf die RHS anzuwenden, was im Wesentlichen das ist, wofür ich sie hier neu bestimmt habe.
Richard Hodges
6
Ich denke nicht, dass das ein gutes Argument ist, sorry. Zunächst einmal hat die Verkettung von Zeichenfolgen eine völlig andere Semantik als die Addition (sie ist nicht einmal kommutativ!). Zweitens ist Ihre Logik nach Ihrer Logik <<ein Bitverschiebungsoperator, kein "Stream-Out" -Operator. Das Problem bei Ihrer Neudefinition ist nicht, dass sie nicht mit den vorhandenen Bedeutungen des Operators übereinstimmt, sondern dass sie bei einem einfachen Funktionsaufruf keinen Wert hinzufügt.
Konrad Rudolph
8
<<scheint eine schlimme Überlastung zu sein. Ich würde tun !invert= x;;)
Yakk - Adam Nevraumont
12
@ Yakk-AdamNevraumont LOL, jetzt hast du mich angefangen: godbolt.org/z/KIkesp
Richard Hodges
37

Solange wir Assemblersprache enthalten ...

FORTH

INVERT für eine bitweise Ergänzung.

0= für eine logische (wahr / falsch) Ergänzung.

DrSheldon
quelle
4
Es ist fraglich, ob in jeder stapelorientierten Sprache ein not
Unary
2
Sicher, es ist eine etwas seitwärts gerichtete Antwort, da das OP klar über C-ähnliche Sprachen nachdenkt, die eine traditionelle Zuweisungserklärung haben. Ich denke, seitwärts gerichtete Antworten wie diese haben Wert, wenn auch nur, um einen Teil der Programmierwelt zu zeigen, an den der Leser möglicherweise nicht gedacht hat. ("Es gibt mehr Programmiersprachen im Himmel und auf Erden, Horatio, als in unserer Philosophie geträumt werden.")
Wayne Conrad
31

Das Dekrementieren eines C99 boolhat den gewünschten Effekt, ebenso wie das Inkrementieren oder Dekrementieren der bitTypen, die in einigen Dialekten mit winzigen Mikrocontrollern unterstützt werden (die, wie ich beobachtet habe, Bits als ein Bit breite Bitfelder behandeln, sodass alle geraden Zahlen auf 0 und alle abgeschnitten werden ungerade Zahlen zu 1). Ich würde eine solche Verwendung nicht besonders empfehlen, zum Teil, weil ich kein großer Fan der boolTypensemantik bin [IMHO sollte der Typ angegeben haben, dass sich ein boolWert, für den ein anderer Wert als 0 oder 1 gespeichert ist, beim Lesen möglicherweise so verhält es enthält einen nicht spezifizierten (nicht unbedingt konsistenten) ganzzahligen Wert; Wenn ein Programm versucht, einen ganzzahligen Wert zu speichern, von dem nicht bekannt ist, dass er 0 oder 1 ist, sollte er !!ihn zuerst verwenden.

Superkatze
quelle
2
C ++ hat dasselbe bool bis C ++ 17 gemacht .
Davis Herring
31

Assemblersprache

NOT eax

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

Adrian
quelle
2
Ich denke nicht, dass dies genau das ist, was op im Sinn hatte, vorausgesetzt, dies ist eine x86-Assembly. zB en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA
8
Sie können stattdessen alle Bits verwenden: NICHT 0x00000000 = 0xFFFFFFFF
Adrian
5
Ja, obwohl ich versucht habe, die Unterscheidung zwischen booleschen und bitweisen Operatoren zu treffen. Angenommen, die Sprache hat einen genau definierten booleschen Typ: "(also kein C-Stil int a = TRUE)."
BurnsBA
5
@BurnsBA: In der Tat haben Assemblersprachen keinen einzigen Satz genau definierter Wahrheits- / Falschwerte. Bei Code-Golf wurde viel darüber diskutiert, welche Werte Sie für boolesche Probleme in verschiedenen Sprachen zurückgeben dürfen. Da asm kein if(x)Konstrukt hat , ist es völlig vernünftig, 0 / -1 als false / true zuzulassen, wie bei SIMD-Vergleichen ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ). Aber Sie haben Recht, dass dies nicht funktioniert, wenn Sie es für einen anderen Wert verwenden.
Peter Cordes
4
Es ist schwierig, eine einzige boolesche Darstellung zu haben, da PCMPEQW 0 / -1 ergibt, SETcc jedoch 0 / + 1.
Eugene Styer
28

Ich gehe davon aus, dass Sie keine Sprache wählen werden, die nur darauf basiert :-) Auf jeden Fall können Sie dies in C ++ mit so etwas wie:

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

Siehe das folgende vollständige Programm zum Beispiel:

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

Dies gibt wie erwartet aus:

false
true
false
paxdiablo
quelle
2
Möchtest du return b = !bauch? Jemand, der liest foo = makenot(x) || yoder nur eine einfache, foo = makenot(bar)könnte annehmen, dass dies makenoteine reine Funktion war, und annehmen, dass es keine Nebenwirkungen gab. Das Vergraben eines Nebeneffekts in einem größeren Ausdruck ist wahrscheinlich ein schlechter Stil, und der einzige Vorteil eines nicht leeren Rückgabetyps besteht darin, dies zu ermöglichen.
Peter Cordes
2
@MatteoItalia schlägt vor template<typename T> T& invert(T& t) { t = !t; return t; } , welche Vorlage in / von konvertiert wird, boolwenn sie für ein Nicht- boolObjekt verwendet wird. IDK, ob das gut oder schlecht ist. Vermutlich gut.
Peter Cordes
21

Visual Basic.Net unterstützt dies über eine Erweiterungsmethode.

Definieren Sie die Erweiterungsmethode folgendermaßen:

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

Und dann nenne es so:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Ihr ursprüngliches Beispiel würde also ungefähr so ​​aussehen:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip
Reginald Blue
quelle
2
Während dies als die vom Autor gesuchte Abkürzung fungiert, müssen Sie natürlich zwei Operatoren verwenden, um zu implementieren Flip(): =und Not. Es ist interessant zu erfahren, dass es beim Aufrufen auch SomeInstance.SomeBoolean.Flipdann funktioniert, wenn SomeBooleanes sich um eine Eigenschaft handelt, während der entsprechende C # -Code nicht kompiliert werden würde.
Speck
14

In Rust können Sie Ihr eigenes Merkmal erstellen, um die Typen zu erweitern, die das NotMerkmal implementieren :

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

Sie können auch ^= truewie vorgeschlagen verwenden, und im Fall von Rust gibt es kein mögliches Problem, da dies falsekeine "getarnte" Ganzzahl wie in C oder C ++ ist:

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

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

In Python

Python unterstützt solche Funktionen, wenn die Variable mit dem Operator den Bool-Typ (True oder False) hat exclusive or (^=):

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

In C # :

boolean.variable.down.here ^= true;

Der boolesche ^ -Operator ist XOR, und XORing mit true ist dasselbe wie Invertieren.

Rakensi
quelle