Czy jakikolwiek język ma jednoargumentowy operator przełączania logicznego?


141

Jest to więc bardziej teoretyczne pytanie. C ++ i języki (in) bezpośrednio na nim oparte (Java, C #, PHP) mają operatory skrótów do przypisywania wyniku większości operatorów binarnych do pierwszego operandu, takie jak

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

ale kiedy chcę zmienić wyrażenie boolowskie, zawsze piszę coś takiego

a = !a;

co staje się irytujące, gdy ajest to długie wyrażenie.

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

(tak, prawo Demeter, wiem).

Zastanawiałem się więc, czy istnieje język z jednoargumentowym operatorem przełączania typu boolean , który pozwoliłby mi skrócić a = !abez powtarzania wyrażenia a, na przykład

!=a;  
// or
a!!;

Załóżmy, że nasz język ma odpowiedni typ boolowski (jak boolw C ++) i to ajest tego typu (więc nie ma stylu C int a = TRUE).

Jeśli możesz znaleźć udokumentowane źródło, chciałbym się również dowiedzieć, czy np. Projektanci C ++ rozważali dodanie takiego operatora, kiedy boolstał się typem wbudowanym, a jeśli tak, to dlaczego zdecydowali się na nie.


(Uwaga: Wiem, że niektórzy ludzie są zdania, że zadanie nie należy używać =i że ++i +=nie są użyteczne, ale operatorzy wady projektowe; niech tylko zakładać, że jestem zadowolony z nich i skupić się dlaczego oni nie rozciąga się bools).


31
A co z funkcją void Flip(bool& Flag) { Flag=!Flag; }Skraca twoje długie wyrażenie.
harper

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

13
długie wyrażenia można przypisać do odwołań w celu uproszczenia kodu
Quentin 2

6
@KamilCuk To może działać, ale mieszasz typy. Przypisujesz liczbę całkowitą do bool.
harper

7
@ user463035818 jest *= -1jednak, co z jakiegoś powodu wydaje mi się bardziej intuicyjne niż ^= true.
CompuChip

Odpowiedzi:


15

To pytanie jest rzeczywiście interesujące z czysto teoretycznego punktu widzenia. Pomijając, czy jednoargumentowy, mutujący operator przełączający byłby przydatny, czy też dlaczego wiele języków zdecydowało się go nie udostępniać, zaryzykowałem poszukiwanie, czy rzeczywiście istnieje.

TL; DR najwyraźniej nie, ale Swift pozwala na wdrożenie jednego. Jeśli chcesz tylko zobaczyć, jak to się robi, możesz przewinąć do końca tej odpowiedzi.


Po (szybkim) wyszukiwaniu funkcji w różnych językach mogę śmiało powiedzieć, że żaden język nie zaimplementował tego operatora jako ścisłej operacji mutacji w miejscu (popraw mnie, jeśli go znajdziesz). Więc następną rzeczą byłoby sprawdzenie, czy istnieją języki, w których można go zbudować. Wymagałoby to dwóch rzeczy:

  1. możliwość implementacji (jednoargumentowych) operatorów z funkcjami
  2. zezwalanie tym funkcjom na posiadanie argumentów typu pass-by-reference (aby mogły bezpośrednio mutować swoje argumenty)

Wiele języków zostanie natychmiast wykluczonych ze względu na brak obsługi jednego lub obu tych wymagań. Java dla one nie zezwala na przeciążanie operatorów (lub operatorów niestandardowych), a ponadto wszystkie typy pierwotne są przekazywane przez wartość. Go w ogóle nie obsługuje przeciążania operatorów (z wyjątkiem hacków ). Rust zezwala na przeciążanie operatorów tylko dla typów niestandardowych. Możesz to prawie osiągnąć w Scali , która pozwala ci używać bardzo twórczo nazwanych funkcji, a także pomijać nawiasy, ale niestety nie ma żadnego odniesienia do przekazywania. języka Fortran jest bardzo blisko, ponieważ pozwala na niestandardowych operatorów, ale wyraźnie zabrania im inout parametry (które są dozwolone w normalnych funkcjach i podprogramach).


Jest jednak co najmniej jeden język, który spełnia wszystkie niezbędne warunki: Swift . Chociaż niektórzy ludzie utworzyli linki do nadchodzącej funkcji składowej .toggle () , możesz także napisać własny operator, który rzeczywiście obsługuje argumenty inout . Oto i oto:

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false


3
Tak, ale pytanie dotyczyło operatora , a nie funkcji członkowskiej.
Lauri Piispanen

4
„Rdza też nie” => Tak
Boiethios

3
@Boiethios thanks! Edytowane dla Rusta - zezwala jednak tylko na przeciążone operatory dla niestandardowych typów, więc nie ma kości.
Lauri Piispanen

3
@LauriPiispanen Zasada jest bardziej ogólna, a ze względu na panowanie sieroty , FYI
Boiethios

143

Przełączanie bitu boolowskiego

... to pozwoliłoby mi skrócić a = !a bez powtarzania wyrażenia dlaa ...

To podejście nie jest tak naprawdę czystym operatorem „mutującego odwrócenia”, ale spełnia powyższe kryteria; prawa strona wyrażenia nie obejmuje samej zmiennej.

Każdy język z logicznym przypisaniem XOR (np. ^=) Pozwoliłby na odwrócenie bieżącej wartości zmiennej, powiedzmy a, za pomocą przypisania XOR do true:

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

Jak zauważył @cmaster w komentarzach poniżej, powyższe zakłada, że ajest to typ bool, a nie np. Liczba całkowita lub wskaźnik. Jeśli aw rzeczywistości jest czymś innym (np. boolCzymś nieoceniającym do „prawdziwej” lub „fałszywej” wartości, z reprezentacją bitową, która nie jest 0b1lub 0b0, odpowiednio), powyższe nie obowiązuje.

Na przykład Java jest językiem, w którym jest dobrze zdefiniowany i nie podlega żadnym cichym konwersjom. Cytując komentarz @ Boann z dołu:

W Java ^i ^=są wyraźnie określone dla zachowania logicznych i liczb ( 15.22.2. Logicznych operatorów logicznych &, ^i| ), przy czym zarówno obie boczne części operator musi mieć wartości logiczne lub obie strony muszą być liczbami całkowitymi. Nie ma cichej konwersji między tymi typami. Więc nie będzie cicho działać nieprawidłowo, jeśli azostanie zadeklarowana jako liczba całkowita, ale raczej spowoduje błąd kompilacji. Więc a ^= true;jest bezpieczny i dobrze zdefiniowany w Javie.


Szybki: toggle()

W wersji Swift 4.2 przyjęto i wdrożono następującą propozycję ewolucji:

To dodaje natywną toggle()funkcję do Booltypu w Swift.

toggle()

Przełącza wartość zmiennej boolowskiej.

Deklaracja

mutating func toggle()

Dyskusja

Użyj tej metody, aby przełączyć wartość logiczną od truedo falselub od falsedo true.

var bools = [true, false]

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

Nie jest to operator sam w sobie, ale pozwala na natywne podejście języka do przełączania wartości logicznych.


3
Komentarze nie służą do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Samuel Liew

44

W C ++ można popełnić kardynalny grzech przedefiniowania znaczenia operatorów. Mając to na uwadze i trochę ADL, wszystko, co musimy zrobić, aby uwolnić chaos w naszej bazie użytkowników, to:

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

oczekiwany wynik:

6
0
1
0
1

9
„Kardynał grzech przedefiniowania znaczenia operatorów” - rozumiem, że przesadziłeś, ale tak naprawdę nie jest grzechem kardynalnym ponowne nadanie nowego znaczenia istniejącym operatorom w ogóle.
Konrad Rudolph

5
@KonradRudolph Co mam przez to na myśli, oczywiście, że operator powinien mieć logiczny sens w odniesieniu do jego zwykłego znaczenia arytmetycznego lub logicznego. 'operator +' dla 2 łańcuchów jest logiczne, ponieważ konkatenacja jest podobna (naszym zdaniem) do dodawania lub akumulacji. operator<<zaczęło oznaczać „przesyłane strumieniowo” z powodu pierwotnego nadużycia w STL. Nie będzie to naturalnie oznaczać „zastosuj funkcję transformacji na RHS”, co zasadniczo jest tym, do czego ponownie ją tutaj zamierzyłem.
Richard Hodges,

6
Nie sądzę, żeby to był dobry argument, przepraszam. Po pierwsze, konkatenacja ciągów ma zupełnie inną semantykę niż dodawanie (nie jest nawet przemienna!). Po drugie, według twojej logiki <<jest to operator przesunięcia bitowego, a nie operator „wypływu”. Problem z twoją redefinicją nie polega na tym, że jest ona niespójna z istniejącymi znaczeniami operatora, ale raczej na tym, że nie dodaje ona żadnej wartości do prostego wywołania funkcji.
Konrad Rudolph

8
<<wydaje się być dużym przeciążeniem. Zrobiłbym !invert= x;;)
Yakk - Adam Nevraumont

12
@ Yakk-AdamNevraumont LOL, teraz masz mnie zacząć: godbolt.org/z/KIkesp
Richard Hodges

37

Jeśli włączymy język asemblera ...

NAPRZÓD

INVERT do bitowego uzupełnienia.

0= dla logicznego (prawda / fałsz) uzupełnienia.


4
Dyskusyjne, w każdym języku zorientowanym na stosy jednoargumentowe not jest operatorem "przełączającym" :-)
Bergi

2
Jasne, jest to trochę boczna odpowiedź, ponieważ OP wyraźnie myśli o językach podobnych do C, które mają tradycyjną instrukcję przypisania. Myślę, że takie poboczne odpowiedzi mają wartość, choćby po to, by pokazać część świata programowania, o której czytelnik mógłby nie pomyśleć. („Na niebie i na ziemi jest więcej języków programowania, Horatio, niż jest to wymarzone w naszej filozofii”)
Wayne Conrad

31

Zmniejszanie C99 bool przyniesie pożądany efekt, podobnie jak zwiększenie lub zmniejszenie bittypów obsługiwanych w niektórych dialektach z małymi mikrokontrolerami (które z tego, co zaobserwowałem, traktują bity jako pola bitowe o szerokości jednego bitu, więc wszystkie liczby parzyste są obcinane do 0 i wszystkie liczby nieparzyste do 1). Nie polecałbym szczególnie takiego użycia, po części dlatego, że nie jestem wielkim fanem boolsemantyki typu [IMHO, typ powinien był określićże a booldo którego zapisana jest wartość inna niż 0 lub 1 może zachowywać się po przeczytaniu zawiera nieokreśloną (niekoniecznie spójną) wartość całkowitą; jeśli program próbuje zapisać wartość całkowitą, o której nie wiadomo, czy jest równa 0 lub 1, powinien !!najpierw użyć na niej].


2
C ++ zrobił to samo z bool aż do C ++ 17 .
Davis Herring

31

2
Nie sądzę, żeby to było dokładnie to, co OP miał na myśli, zakładając, że jest to montaż x86. np. en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA

8
Możesz zamiast tego użyć wszystkich bitów: NOT 0x00000000 = 0xFFFFFFFF
Adrian

5
Cóż, tak, chociaż próbowałem rozróżnić operatory logiczne i bitowe. Jak op mówi w pytaniu, załóżmy, że język ma dobrze zdefiniowany typ boolowski: „(więc nie int a w stylu C = TRUE)”.
BurnsBA,

5
@BurnsBA: rzeczywiście, języki asemblera nie mają ani jednego zestawu dobrze zdefiniowanych prawdziwych / fałszywych wartości. W trakcie kodowania golfa wiele dyskutowano na temat wartości, które można zwracać w przypadku problemów logicznych w różnych językach. Ponieważ asm nie ma if(x)konstrukcji , całkowicie rozsądne jest zezwolenie na 0 / -1 jako fałsz / prawda, jak przy porównaniach SIMD ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ). Ale masz rację, że to się psuje, jeśli użyjesz go do jakiejkolwiek innej wartości.
Peter Cordes

4
Trudno jest mieć pojedynczą reprezentację logiczną, biorąc pod uwagę, że PCMPEQW daje wynik 0 / -1, ale SETcc daje wynik 0 / + 1.
Eugene Styer

28

Zakładam, że nie będziesz wybierać języka opartego wyłącznie na tym :-) W każdym razie możesz to zrobić w C ++ za pomocą czegoś takiego:

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

Zobacz na przykład następujący kompletny program:

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

To daje, zgodnie z oczekiwaniami:

false
true
false

2
Czy ty też byś chciał return b = !b? Ktoś czytający foo = makenot(x) || ylub po prostu prosty foo = makenot(bar)mógłby założyć, że makenotto czysta funkcja i założyć, że nie ma żadnego efektu ubocznego. Zagrzebanie efektu ubocznego w większym wyrażeniu jest prawdopodobnie złym stylem, a jedyną korzyścią wynikającą z braku pustego typu powrotu jest umożliwienie tego.
Peter Cordes,

2
@MatteoItalia sugeruje template<typename T> T& invert(T& t) { t = !t; return t; } , który szablon zostanie przekonwertowany na / z, booljeśli zostanie użyty na boolobiekcie niebędącym obiektem. IDK, czy to dobrze, czy źle. Prawdopodobnie dobrze.
Peter Cordes


21

Visual Basic.Net obsługuje to poprzez metodę rozszerzenia.

Zdefiniuj metodę rozszerzenia w następujący sposób:

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

A potem nazwij to tak:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Twój oryginalny przykład wyglądałby mniej więcej tak:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip

2
Chociaż działa to jako skrót, którego szukał autor, oczywiście musisz użyć dwóch operatorów do zaimplementowania Flip(): =i Not. Warto się dowiedzieć, że w przypadku wywołania SomeInstance.SomeBoolean.Flipdziała, nawet jeśli SomeBooleanjest właściwością, podczas gdy równoważny kod C # nie będzie się kompilował.
BACON

14

W Rust możesz stworzyć własną cechę, aby rozszerzyć typy, które ją implementują 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);
}

Możesz również użyć ^= truezgodnie z sugestią, aw przypadku Rusta nie ma problemu, aby to zrobić, ponieważ falsenie jest to „zamaskowana” liczba całkowita, jak w C lub C ++:

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

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


3

W C # :

boolean.variable.down.here ^= true;

Operatorem boolowskim ^ jest XOR, a XOR z wartością true jest tym samym, co odwracanie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.