Есть ли в каком-либо языке унарный логический оператор переключения?

140

Так что это скорее теоретический вопрос. C ++ и языки (в), непосредственно основанные на нем (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стал встроенным типом, и если да, то почему они отказались от него.


(Примечание: я знаю, что некоторые люди считают, что присваивание не следует использовать =и что операторы не являются полезными , ++а +=являются недостатками дизайна; давайте просто предположим, что я доволен ими, и сосредоточимся на том, почему они не распространяются на bools).

CompuChip
источник
31
А как насчет функции void Flip(bool& Flag) { Flag=!Flag; }The сокращает ваше длинное выражение.
harper
72
this.dataSource.trackedObject.currentValue.booleanFlag ^= 1;
KamilCuk
13
длинные выражения могут быть назначены ссылкам для упрощения кода
Квентин 2,
6
@KamilCuk Это может сработать, но вы путаете типы. Вы назначаете целое число bool.
harper
7
@ user463035818 есть, *= -1но по некоторым причинам я считаю его более интуитивным, чем ^= true.
CompuChip

Ответы:

15

Этот вопрос действительно интересен с чисто теоретической точки зрения. Не говоря уже о том, будет ли полезен унарный, мутирующий логический оператор переключения , или почему многие языки предпочли его не предоставлять, я рискнул проверить, существует ли он на самом деле.

TL; DR, очевидно, нет, но Swift позволяет вам реализовать его. Если вы хотите только увидеть, как это делается, прокрутите этот ответ до конца.


После (быстрого) поиска функций различных языков я с уверенностью могу сказать, что ни один язык не реализовал этот оператор как операцию строгого изменения на месте (исправьте меня, если вы ее найдете). Итак, следующим шагом будет посмотреть, есть ли языки, которые позволят вам создать такой язык. Для этого потребуются две вещи:

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

Многие языки будут немедленно исключены за то, что не поддерживают одно или оба этих требования. Например, Java не допускает перегрузки операторов (или пользовательских операторов), и, кроме того, все примитивные типы передаются по значению. Go вообще не поддерживает перегрузку оператора (кроме как путем взлома ). Rust допускает перегрузку операторов только для пользовательских типов. Вы могли бы почти достичь этого в Scala , который позволяет вам использовать очень креативно названные функции, а также опускать скобки, но, к сожалению, здесь нет передачи по ссылке. Фортран очень близок в том, что он позволяет использовать пользовательские операторы, но специально запрещает им иметь inout параметры (которые разрешены в обычных функциях и подпрограммах).


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

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false
Лаури Пииспанен
источник
6
Язык предоставит это: github.com/apple/swift-evolution/blob/master/proposals/…
Cristik
3
Да, но вопрос был задан специально для оператора , а не для функции-члена.
Lauri Piispanen
4
«Ржавчина тоже» => Да, есть
Бойэтиос
3
@Boiethios, спасибо! Отредактировано для Rust - допускает только перегруженные операторы для пользовательских типов, так что никаких кубиков.
Лаури Пииспанен
3
@LauriPiispanen Правило более общее, и из-за правила сирот , к вашему сведению
Бойетиос
144

Переключение логического бита

... это позволило бы мне сократить, 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оцениваемое до «правдивого» или «ложного» значения, с битовым представлением, которое не является 0b1или 0b0, соответственно), вышеуказанное не выполняется.

В качестве конкретного примера, Java - это язык, в котором он четко определен и не подлежит никаким преобразованиям без вывода сообщений. Цитата из комментария @Boann снизу:

В Java ^и ^=имеют явно определенное поведение для логических и целых чисел ( 15.22.2. Логические операторы Boolean &, ^и| ), где либо обе стороны оператора должны быть логическими, либо обе стороны должны быть целыми числами. Между этими типами нет тихого преобразования. Таким образом, если aон объявлен как целое число, он не будет работать тихо , а скорее выдаст ошибку компиляции. Так a ^= true;безопасно и хорошо определены в Java.


Swift: toggle()

Начиная с Swift 4.2, следующее предложение по развитию было принято и реализовано:

Это добавляет встроенную toggle()функцию к Boolтипу в Swift.

toggle()

Переключает значение логической переменной.

Декларация

mutating func toggle()

Обсуждение

Используйте этот метод для переключения логического значения с trueна falseили с falseна true.

var bools = [true, false]

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

Сам по себе это не оператор, но он позволяет использовать родной язык для логического переключения.

дфриб
источник
3
Комментарии не предназначены для расширенного обсуждения; этот разговор был перемещен в чат .
Сэмюэл Лью
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<<стало означать «потоковое» из-за его изначального злоупотребления в STL. Естественно, это не будет означать «применить функцию преобразования к правой части экрана», для чего я, по сути, изменил ее здесь.
Ричард Ходжес
6
Я не думаю, что это хороший аргумент, извините. Во-первых, конкатенация строк имеет совершенно другую семантику, чем сложение (она даже не коммутативна!). Во-вторых, по вашей логике <<это оператор битового сдвига, а не оператор «вывода потока». Проблема с вашим переопределением не в том, что оно несовместимо с существующими значениями оператора, а в том, что оно не добавляет значения по сравнению с простым вызовом функции.
Конрад Рудольф
8
<<похоже на плохую перегрузку. Я бы сделал !invert= x;;)
Якк - Адам Неврамонт
12
@ Yakk-AdamNevraumont LOL, теперь вы меня начали: godbolt.org/z/KIkesp
Ричард Ходжес
37

Пока мы включаем ассемблер ...

Четвертый

INVERT для побитового дополнения.

0= для логического (истина / ложь) дополнения.

Доктор Шелдон
источник
4
Можно утверждать, что в каждом стек-ориентированном языке унарный notоператор является оператором переключения :-)
Берги
2
Конечно, это немного косой ответ, поскольку OP явно думает о C-подобных языках, которые имеют традиционный оператор присваивания. Я думаю, что подобные ответы имеют ценность, хотя бы для того, чтобы показать часть мира программирования, о которой читатель мог не подумать. («На небе и на земле, Горацио, существует больше языков программирования, чем мечтает наша философия».)
Уэйн Конрад
31

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

суперкар
источник
2
C ++ делал то же самое bool до C ++ 17 .
Дэвис Херринг,
31

язык ассемблера

NOT eax

См. Https://www.tutorialspoint.com/assembly_programming/assembly_logical_instructions.htm

Адриан
источник
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)».
BurnsBA
5
@BurnsBA: действительно, языки ассемблера не имеют единого набора четко определенных истинных / ложных значений. На code-golf много обсуждалось о том, какие значения разрешено возвращать для логических задач на разных языках. Поскольку asm не имеет if(x)конструкции , вполне разумно разрешить 0 / -1 как false / true, как для сравнения 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
Paxdiablo
источник
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, хорошо это или плохо. Предположительно хорошо.
Питер Кордес
22

PostScript , будучи конкатенативным , стек-ориентированный язык , как Forth, имеет одноместную тумблер, не . Оператор not переключает значение на вершине стека. Например,

true    % push true onto the stack
not     % invert the top of stack
        % the top of stack is now false

См. Справочное руководство по языку PostScript (pdf) , стр. 458.

Уэйн Конрад
источник
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как было предложено, и в случае Rust нет никаких проблем с этим, потому что 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);
}
Бойэтиос
источник
6

В Python

Python поддерживает такую ​​функциональность, если переменная имеет тип bool (True или False) с exclusive or (^=)оператором:

a = False
a ^= True
print(a)  # --> True
a ^= True
print(a)  # --> False
Андрей Иванейко
источник
3

В C # :

boolean.variable.down.here ^= true;

Логический оператор ^ - это XOR, а операция XOR с истиной аналогична инвертированию.

ракенси
источник