Algum idioma possui um operador de alternância booleano unário?

141

Portanto, essa é mais uma questão teórica. C ++ e linguagens (in) diretamente baseadas nele (Java, C #, PHP) possuem operadores de atalho para atribuir o resultado da maioria dos operadores binários ao primeiro operando, como

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

mas quando quero alternar uma expressão booleana, sempre me pego escrevendo algo como

a = !a;

o que fica irritante quando aé uma expressão longa como.

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

(sim, a Lei de Demeter, eu sei).

Então, eu estava pensando, existe algum idioma com um operador de alternância booleano unário que me permita abreviar a = !asem repetir a expressão a, por exemplo

!=a;  
// or
a!!;

Vamos supor que nossa linguagem tenha um tipo booleano adequado (como boolem C ++) e esse aseja (sem estilo C int a = TRUE).

Se você pode encontrar uma fonte documentada, eu também estaria interessado em saber se, por exemplo, os designers de C ++ consideraram adicionar um operador como esse quando boolse tornou um tipo interno e, se sim, por que decidiram contra.


(Nota: Eu sei que algumas pessoas são da opinião de que a atribuição não deve usar =e que ++e +=não são operadores útil, mas falhas de projeto, vamos apenas supor que eu estou feliz com eles e foco em por que eles não se estenderia até bools).

CompuChip
fonte
31
Que tal uma função? void Flip(bool& Flag) { Flag=!Flag; }Encurta sua longa expressão.
quer
72
this.dataSource.trackedObject.currentValue.booleanFlag ^= 1;
KamilCuk
13
expressões longos pode ser atribuído a referências para simplificar o código
Quentin 2
6
@KamilCuk Isso pode funcionar, mas você mistura tipos. Você atribui um número inteiro a um bool.
quer
7
@ user463035818 existe *= -1, porém, que por algum motivo eu acho mais intuitivo do que ^= true.
21818

Respostas:

15

Esta questão é realmente interessante do ponto de vista puramente teórico. Deixando de lado se um operador booleano alternador unário ou mutante seria útil ou por que muitos idiomas optaram por não fornecer um, aventurei-me em uma missão para ver se ele realmente existe ou não.

TL; DR aparentemente não, mas Swift permite implementar um. Se você quiser apenas ver como isso é feito, role para a parte inferior desta resposta.


Após uma pesquisa (rápida) em recursos de vários idiomas, eu me sentiria seguro em dizer que nenhum idioma implementou esse operador como uma operação no local rigorosa de mutação (corrija-me se encontrar um). Portanto, o próximo passo seria verificar se existem idiomas que permitem a criação de um. O que isso exigiria é duas coisas:

  1. ser capaz de implementar operadores (unários) com funções
  2. permitindo que essas funções tenham argumentos de passagem por referência (para que eles possam alterar seus argumentos diretamente)

Muitos idiomas serão descartados imediatamente por não suportar um ou ambos os requisitos. O Java for one não permite sobrecarga do operador (ou operadores customizados) e, além disso, todos os tipos primitivos são passados ​​por valor. O Go não oferece suporte à sobrecarga do operador (exceto por hacks ). A ferrugem apenas permite sobrecarga do operador para tipos personalizados. Você quase conseguiu isso no Scala , que permite usar funções nomeadas de maneira muito criativa e também omitir parênteses, mas, infelizmente, não há passagem por referência. O Fortran fica muito próximo, pois permite operadores personalizados, mas proíbe-os especificamente de terem acesso parâmetros (permitidos em funções e sub-rotinas normais).


No entanto, há pelo menos um idioma que marca todas as caixas necessárias: Swift . Embora algumas pessoas tenham se vinculado à próxima função de membro .toggle () , você também pode escrever seu próprio operador, o que realmente suporta argumentos inout . Eis e eis que:

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false
Lauri Piispanen
fonte
3
Sim, mas a pergunta especificamente pedia um operador , não uma função membro.
Lauri Piispanen
4
"Nem ferrugem" => Sim, sim
Boiethios 30/08/18
3
@Boiethios thanks! Editado para Rust - permite apenas operadores sobrecarregados para tipos personalizados, portanto, não há dados.
Lauri Piispanen
3
@LauriPiispanen A regra é mais geral do que isso e, devido à regra dos órfãos , FYI
Boiethios
143

Alternando o bit booleano

... isso me permitiria abreviar a = !a sem repetir a expressão paraa ...

Essa abordagem não é realmente um operador "flip mutante" puro, mas atende aos seus critérios acima; o lado direito da expressão não envolve a própria variável.

Qualquer idioma com uma atribuição XOR booleana (por exemplo ^=) permitiria inverter o valor atual de uma variável, digamos a, por meio da atribuição XOR para true:

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

Conforme apontado por @cmaster nos comentários abaixo, o acima pressupõe que aé do tipo boole não, por exemplo, um número inteiro ou um ponteiro. Se aé de fato outra coisa (por exemplo, algo que não boolavalia um valor "verdade" ou "falsidade", com uma representação de bits que não é 0b1ou 0b0, respectivamente), o acima não se aplica.

Para um exemplo concreto, Java é uma linguagem em que isso é bem definido e não está sujeito a nenhuma conversão silenciosa. Citando o comentário de @ Boann abaixo:

Em Java, ^e ^=tem comportamento definido explicitamente para booleans e para inteiros ( 15.22.2. Operadores lógicos booleanos &, ^e| ), caso a ambos os lados do operador deve ser booleans, ou ambos os lados devem ser inteiros. Não há conversão silenciosa entre esses tipos. Portanto, ele não funcionará silenciosamente se afor declarado como um número inteiro, mas sim com um erro de compilação. Então, a ^= true;é seguro e bem definido em Java.


Rápido: toggle()

A partir do Swift 4.2, a seguinte proposta de evolução foi aceita e implementada:

Isso adiciona uma toggle()função nativa ao Booltipo no Swift.

toggle()

Alterna o valor da variável booleana.

Declaração

mutating func toggle()

Discussão

Use este método para alternar um valor booleano de truepara falseou de falsepara true.

var bools = [true, false]

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

Este não é um operador, por si só, mas permite uma abordagem nativa do idioma para alternância booleana.

dfri
fonte
3
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Samuel Liew
44

Em C ++, é possível comprometer o Cardinal Sin de redefinir o significado dos operadores. Com isso em mente e um pouco de ADL, tudo o que precisamos fazer para liberar o caos em nossa base de usuários é o seguinte:

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

resultado esperado:

6
0
1
0
1
Richard Hodges
fonte
9
“O cardeal pecado de redefinir o significado de operadores” - Eu entendo que você estavam exagerando, mas é realmente não um pecado capital para reatribuir novos significados para os operadores existentes em geral.
Konrad Rudolph
5
@KonradRudolph O que quero dizer com isso é claro que um operador deve fazer sentido lógico em relação ao seu significado aritmético ou lógico usual. 'operator +' para 2 strings é lógico, pois a concatenação é semelhante (em nossa opinião) a adição ou acumulação. operator<<passou a significar 'transmitido' por causa de seu abuso original no STL. Naturalmente, isso não significa 'aplicar a função de transformação no RHS', que é essencialmente o que eu propus para ele aqui.
Richard Hodges
6
Não acho que seja um bom argumento, desculpe. Primeiro, a concatenação de strings tem semântica completamente diferente da adição (nem sequer é comutativa!). Em segundo lugar, pela sua lógica, <<é um operador de troca de bits, não um operador de "fluxo contínuo". O problema com sua redefinição não é inconsistente com os significados existentes do operador, mas sim que não agrega valor a uma simples chamada de função.
Konrad Rudolph
8
<<parece uma sobrecarga ruim. Eu faria !invert= x;;)
Yakk - Adam Nevraumont
12
@ Yakk-AdamNevraumont LOL, agora você me iniciou: godbolt.org/z/KIkesp
Richard Hodges
37

Contanto que incluamos linguagem assembly ...

ADIANTE

INVERT para um complemento bit a bit.

0= para um complemento lógico (verdadeiro / falso).

DrSheldon
fonte
4
Discutível, em cada linguagem orientada a pilha um unário noté um operador "toggle" :-)
Bergi
2
Claro, é um pouco de resposta lateral, pois o OP está pensando claramente em idiomas do tipo C que possuem uma declaração de atribuição tradicional. Penso que respostas laterais como essa têm valor, apenas para mostrar uma parte do mundo da programação em que o leitor pode não ter pensado. ("Há mais linguagens de programação no céu e na terra, Horatio, do que se sonha em nossa filosofia.")
Wayne Conrad
31

Decrementar um C99 boolterá o efeito desejado, assim como aumentar ou diminuir os bittipos suportados em alguns dialetos de microcontroladores minúsculos (que pelo que observei tratam bits como campos de bits largos de um bit, para que todos os números pares sejam truncados para 0 e todos números ímpares para 1). Eu particularmente não recomendaria esse uso, em parte porque não sou um grande fã do booltipo semântica [IMHO, o tipo deveria ter especificado que um boolpara o qual qualquer valor diferente de 0 ou 1 é armazenado pode se comportar quando lido como se ele possui um valor inteiro não especificado (não necessariamente consistente); se um programa está tentando armazenar um valor inteiro que não é conhecido como 0 ou 1, ele deve ser usado !!primeiro].

supercat
fonte
2
O C ++ fez a mesma coisa bool até o C ++ 17 .
Davis Herring
31

Linguagem Assembly

NOT eax

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

Adrian
fonte
2
Eu não acho que isso é exatamente o que o op tinha em mente, assumindo que este seja o assembly x86. por exemplo, en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA
8
Você pode usar todos os bits: NOT 0x00000000 = 0xFFFFFFFF
Adrian
5
Bem, sim, embora eu estivesse tentando estabelecer a distinção entre operadores booleanos e bit a bit. Como op diz na pergunta, suponha que o idioma tenha um tipo booleano bem definido: "(portanto, nenhum estilo C int a = TRUE)."
precisa saber é o seguinte
5
@ BurnsBA: na verdade, as linguagens assembly não têm um único conjunto de valores bem definidos de verdade / falsidade. No code-golf, muito se discutiu sobre quais valores você pode retornar para problemas booleanos em vários idiomas. Como o asm não tem uma if(x)construção , é totalmente razoável permitir 0 / -1 como falso / verdadeiro, como nas comparações SIMD ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ). Mas você está certo que isso quebra se você o usar em qualquer outro valor.
Peter Cordes
4
É difícil ter uma única representação booleana, pois o PCMPEQW resulta em 0 / -1, mas o SETcc resulta em 0 / + 1.
Eugene Styer
28

Suponho que você não escolherá uma linguagem baseada apenas nisso :-) De qualquer forma, você pode fazer isso em C ++ com algo como:

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

Veja o seguinte programa completo, por exemplo:

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

Isso gera, conforme o esperado:

false
true
false
paxdiablo
fonte
2
Você gostaria return b = !btambém? Alguém lendo foo = makenot(x) || you apenas um simples foo = makenot(bar)pode assumir que essa makenoté uma função pura e assumir que não houve efeito colateral. Enterrar um efeito colateral dentro de uma expressão maior provavelmente é um estilo ruim, e o único benefício de um tipo de retorno não nulo é permitir isso.
Peter Cordes
2
O @MatteoItalia sugere template<typename T> T& invert(T& t) { t = !t; return t; } que o modelo que será convertido para / de boolse for usado em um não- boolobjeto. IDK se isso é bom ou ruim. Presumivelmente bom.
Peter Cordes
21

O Visual Basic.Net suporta isso por meio de um método de extensão.

Defina o método de extensão da seguinte maneira:

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

E então chame assim:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Portanto, seu exemplo original seria algo como:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip
Reginald Blue
fonte
2
Embora isso funcione como a abreviação que o autor estava procurando, é claro, você deve usar dois operadores para implementar Flip(): =e Not. É interessante saber que, no caso de chamar SomeInstance.SomeBoolean.Flip, funciona mesmo que SomeBooleanseja uma propriedade, enquanto o código C # equivalente não seria compilado.
BACON
14

No Rust, você pode criar sua própria característica para estender os tipos que implementam a Notcaracterística:

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

Você também pode usar ^= truecomo sugerido e, no caso de Rust, não há problema possível para fazer isso porque falsenão é um número inteiro "disfarçado", como em C ou C ++:

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

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

Em Python

O Python suporta essa funcionalidade, se a variável tiver o tipo bool (que é True ou False) com o exclusive or (^=)operador:

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

Em c # :

boolean.variable.down.here ^= true;

O operador booleano ^ é XOR e XOR com true é o mesmo que inverter.

rakensi
fonte