Якась мова має одинарного оператора булевого перемикання?


141

Тож це скоріше теоретичне питання. C ++ і мови (in), безпосередньо засновані на ньому (Java, C #, PHP), мають оператори клавіш швидкого доступу для присвоєння результату більшості бінарних операторів першому операнду, наприклад

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

але коли я хочу переключити булевий вираз, я завжди виявляю, що пишу щось подібне

a = !a;

який стає дратівливим, коли aце довго вираження.

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

(так, Закон Деметра, я знаю).

Тож мені було цікаво, чи існує мова з одинарним булевим оператором перемикання, яка дозволила б мені скоротити, a = !aне повторюючи вираз a, наприклад

!=a;  
// or
a!!;

Припустимо, що наша мова має належний буловий тип (як boolу C ++) і aтакий тип (так що немає C-стилю int a = TRUE).

Якщо ви можете знайти документально підтверджене джерело, мені також було б цікаво дізнатися, чи, наприклад, дизайнери C ++ розглядали можливість додавання такого оператора, коли він boolстав вбудованим типом, і якщо так, то чому вони вирішили проти цього.


(Примітка. Я знаю, що деякі люди вважають, що завдання не повинні використовувати =, ++і +=це не корисні оператори, але недоліки в дизайні; давайте просто припустимо, що я задоволений ними, і зосередимось на тому, чому вони не поширюватимуться на загальну кількість).


31
Як щодо функції void Flip(bool& Flag) { Flag=!Flag; }Скорочує ваше довге вираження.
харпер

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

13
довгі вирази можуть бути призначені посиланнями для спрощення коду
Квентін 2

6
@KamilCuk Це може працювати, але ви змішуєте типи. Ви присвоюєте ціле число bool.
Харпер

7
@ user463035818 є *= -1хоч, що мені чомусь інтуїтивно зрозуміло ^= true.
CompuChip

Відповіді:


15

Це питання справді цікаве з чисто теоретичної точки зору. Відміняючи, чи корисним буде чи не одинарний, що мутує мультикульний булевий оператор чи чому багато мов вирішили його не надати, я зважився на пошуки, щоб перевірити, чи існує він чи ні.

TL; DR, мабуть, ні, але Swift дозволяє реалізувати його. Якщо ви хочете побачити, як це робиться, ви можете прокрутити донизу цю відповідь.


Після (швидкого) пошуку особливостей різних мов, я б сміливо сказав, що жодна мова не реалізує цього оператора як сувору мутацію на місці (виправте мене, якщо ви знайдете його). Отже, наступне, що слід побачити, чи існують мови, які дозволяють вам побудувати її. Для цього потрібно дві речі:

  1. вміння реалізовувати (одинарні) оператори з функціями
  2. дозволяючи вказаним функціям мати аргументи прохідних посилань (щоб вони могли безпосередньо вимкнути свої аргументи)

Багато мов одразу виключаються через те, що вони не підтримують жодної або обох цих вимог. Java для одного не дозволяє оператору перевантажувати (або користувацькі оператори), а крім того, всі примітивні типи передаються за значенням. Go не підтримує перевантаження оператора (крім хаків ). Іржа дозволяє лише оператору перевантажувати лише для користувацьких типів. Цього ви могли майже досягти в Scala , який дозволить вам використовувати дуже креативно названі функції, а також пропускати круглі дужки, але, на жаль, немає прохідних посилань. Fortran дуже близький тим, що дозволяє користувальницьким операторам, але спеціально забороняє їм мати inout параметри (які дозволені в нормальних функціях та підпрограмах).


Однак є хоча б одна мова, яка позначає усі необхідні поля: Swift . Незважаючи на те, що деякі люди мають посилання на майбутню функцію- члена .toggle () , ви також можете написати власного оператора, який дійсно підтримує вхідні аргументи. Ось і ось:

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false


3
Так, але питання спеціально задали оператор , а не функція-член.
Лаурі Пійспанен

4
"І не іржавіє" => Так, це
Boiethios

3
@Boiethios дякую! Відредаговано для Rust - дозволяє лише перевантажувати операторів для спеціальних типів, однак без кісток.
Lauri Piispanen

3
@LauriPiispanen Правило є більш загальним, ніж це, і завдяки правилу сиріт , FYI
Boiethios

143

Переміщення булевого шматочка

... це дозволило б мені скоротити a = !a без повторення виразу дляa ...

Цей підхід насправді не є оператором "мутації фліп", але він відповідає вашим вище критеріям; права частина виразу не включає саму змінну.

Будь-яка мова з бульним призначенням XOR (наприклад ^=) дозволила б перевернути поточне значення змінної, скажімо a, за допомогою призначення 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

Як вказує @cmaster в коментарях нижче, вищесказане передбачає aтип bool, а не, наприклад, ціле число чи покажчик. Якщо aнасправді щось інше (наприклад, щось, що не boolоцінює значення "truthy" або "falesy", з бітовим поданням, яке не є 0b1або 0b0, відповідно), вищезгадане не відповідає.

На конкретному прикладі Java - це мова, де це чітко визначено і не підлягає мовчазним перетворенням. Цитуючи коментар @ Boann знизу:

В Java, ^і ^=явним чином визначити поведінку для булевих і для цілих чисел ( 15.22.2. Булевих логічних операторів &, ^і| ), де або обидві сторони оператора повинна бути булевої, або обидва сторін повинні бути цілими числами. Немає тихого перетворення між цими типами. Тож не буде мовчати несправність, якщо aвона оголошена цілим числом, а, скоріше, помилка компіляції. Так a ^= true;безпечно і чітко визначено на Java.


Швидкий: toggle()

Станом на Swift 4.2 прийнята та впроваджена наступна еволюційна пропозиція:

Це додає нативну toggle()функцію Boolтипу в Swift.

toggle()

Перемикає значення змінної Boolean.

Декларація

mutating func toggle()

Обговорення

Цей метод використовується для перемикання логічного значення від trueдо falseабо від falseдо true.

var bools = [true, false]

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

Сам по собі цей оператор не є оператором, але дозволяє використовувати мовний підхід для булевого перемикання.


3
Коментарі не для розширеного обговорення; ця розмова була переміщена до чату .
Самуель Liew

44

У C ++ можна здійснити кардинальний гріх, переосмисливши значення операторів. Зважаючи на це і трохи ADL, все, що нам потрібно зробити, щоб розв'язати хаос на нашій базі користувачів, це:

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

очікуваний вихід:

6
0
1
0
1

9
"Кардинальний гріх переосмислення значення операторів" - Я розумію, що ви перебільшували, але насправді не кардинальний гріх передати новий сенс існуючим операторам взагалі.
Конрад Рудольф

5
@KonradRudolph Що я маю на увазі під цим курсом, що оператор повинен мати логічний сенс щодо його звичайного арифметичного чи логічного значення. 'operator +' для двох рядків логічно, оскільки конкатенація схожа (на наш погляд) з додаванням або накопиченням. operator<<став означати "витік" через своє первісне зловживання STL. Це, природно, не означатиме "застосувати функцію перетворення на RHS", що по суті є те, що я тут передумав.
Річард Ходжес

6
Я не думаю, що це гарний аргумент, вибачте. По-перше, конкатенація рядків має зовсім іншу семантику від додавання (вона навіть не є комутативною!). По-друге, за вашою логікою <<, це оператор зсуву трохи, а не оператор "витікання". Проблема з вашим переосмисленням полягає не в тому, що він не відповідає існуючим значенням оператора, а в тому, що він не додає значення для простого виклику функції.
Конрад Рудольф

8
<<здається поганим перевантаженням. Я б зробив !invert= x;;)
Якк - Адам Невраумон

12
@ Yakk-AdamNevraumont LOL, тепер ви мене розпочали: godbolt.org/z/KIkesp
Річард Ходжес

37

Поки ми включаємо мову складання ...

ЧЕРТИ

INVERT для розрядного доповнення.

0= для логічного (правдивого / хибного) доповнення.


4
Сперечається, що в кожній мові, орієнтованій на стеки, унар notє оператором "toggle" :-)
Bergi,

2
Зрозуміло, це трохи бічна відповідь, оскільки ОП чітко думає про С-подібні мови, які мають традиційне твердження про призначення. Я думаю, що відповіді, подібні до цієї, мають цінність, якби лише показати частину світу програмування, про який читач, можливо, не подумав. ("На небі і на землі, Гораціо, є більше мов програмування, ніж про які мріють у нашій філософії.")
Уейн Конрад

31

Зменшення рівня C99 boolматиме бажаний ефект, як і збільшення або зменшення bitтипів, підтримуваних у деяких діалектах крихітних мікроконтролерів (які, з моменту я спостерігав, розглядають біти як однобітні широкі бітові поля, тому всі парні числа обрізаються до 0 і всі непарні числа до 1). Я б особливо не рекомендував таке використання, частково тому, що я не є великим шанувальником boolсемантики типів [IMHO, тип повинен був вказати, що a, boolдля якого зберігається будь-яке значення, окрім 0 або 1, може поводитись, коли читається як би він містить не визначене (не обов'язково послідовне) ціле значення; якщо програма намагається зберегти ціле значення, яке, як відомо, не дорівнює 0 або 1, !!воно спочатку повинно використовувати його].


2
C ++ робив те саме, що і bool до C ++ 17 .
Девіс Хелінг

31

2
Я не думаю, що це зовсім не те, що op мав на увазі, якщо припустити, що це збірка x86. наприклад, en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA

8
Ви можете використовувати всі біти замість цього: НЕ 0x00000000 = 0xFFFFFFFF
Адріан

5
Ну, так, хоча я намагався провести різницю між булевими та розрядними операторами. Як говорить op у запитанні, припустимо, що мова має чітко визначений булівський тип: "(тому жоден C-стиль int a = TRUE)."
БернсБА

5
@BurnsBA: дійсно, мови монтажу не мають єдиного набору чітко визначених параметрів truthy / falesy. У коді-гольфі багато обговорювалося про те, які значення вам дозволяється повертати за булевими проблемами на різних мовах. Оскільки asm не має if(x)конструкції , цілком розумно дозволити 0 / -1 як помилковий / правдивий, як для порівнянь SIMD ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ). Але ви праві, що це зламається, якщо ви використовуєте його для будь-якого іншого значення.
Пітер Кордес

4
Важко мати єдине булеве представлення, враховуючи, що PCMPEQW призводить до 0 / -1, але SETcc призводить до 0 / + 1.
Євген Стайер

28

Я припускаю, що ви не збираєтесь вибирати мову, що базується виключно на цьому :-) У будь-якому випадку ви можете це зробити в C ++ за допомогою чогось подібного:

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

Дивіться, наприклад, наступну повну програму:

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

Цей результат, як очікувалося:

false
true
false

2
Ви хочете return b = !bтакож? Хтось, хто читає foo = makenot(x) || yабо просто простий, foo = makenot(bar)може припустити, що makenotце чиста функція, і припустити, що побічних ефектів не було. Поховання побічного ефекту всередині більшого виразу, ймовірно, поганий стиль, і єдина перевага недійсного типу повернення - це дозволяє.
Пітер Кордес

2
@MatteoItalia пропонує template<typename T> T& invert(T& t) { t = !t; return t; } , що шаблони будуть перетворюватися в / з, boolякщо вони використовуються на не- boolоб'єкті. IDK, якщо це добре чи погано. Імовірно, добре.
Пітер Кордес


21

Visual Basic.Net підтримує це методом розширення.

Визначте метод розширення так:

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

А потім назвіть це так:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Отже, ваш оригінальний приклад виглядав би приблизно так:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip

2
Хоча це і функціонує так, як автор шукав стенограму, звичайно, для реалізації потрібно використовувати два оператори Flip(): =і Not. Цікаво дізнатися, що у випадку виклику SomeInstance.SomeBoolean.Flipвін працює, навіть якщо SomeBooleanє властивістю, тоді як еквівалентний код C # не збирається.
BACON

14

У Rust ви можете створити свою власну ознаку для розширення типів, що реалізують 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);
}

Ви також можете використовувати так, ^= trueяк пропонується, і у випадку з Іржею це не існує, тому що falseце не "замасковане" ціле число, як у C або C ++:

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

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


3

В C # :

boolean.variable.down.here ^= true;

Булевий ^ оператор - XOR, а XORing з true - це те саме, що інвертування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.