¿Algún idioma tiene un operador de alternancia booleano unario?

141

Entonces esta es más una pregunta teórica. C ++ y los lenguajes (in) directamente basados ​​en él (Java, C #, PHP) tienen operadores de acceso directo para asignar el resultado de la mayoría de los operadores binarios al primer operando, como

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

pero cuando quiero alternar una expresión booleana siempre me encuentro escribiendo algo como

a = !a;

lo que se vuelve molesto cuando aes una expresión larga como.

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

(Sí, la Ley de Demeter, lo sé).

Así que me preguntaba, ¿hay algún lenguaje con un operador de alternancia booleano unario que me permita abreviar a = !asin repetir la expresión para a, por ejemplo

!=a;  
// or
a!!;

Supongamos que nuestro lenguaje tiene un tipo booleano adecuado (como boolen C ++) y que aes de ese tipo (por lo que no tiene estilo C int a = TRUE).

Si puede encontrar una fuente documentada, también me interesaría saber si, por ejemplo, los diseñadores de C ++ han considerado agregar un operador como ese cuando bool convirtió en un tipo incorporado y, de ser así, por qué decidieron no hacerlo.


(Nota: Sé que algunas personas son de la opinión de que la asignación no se debe usar =y que ++y +=no son útiles, pero los operadores de defectos de diseño, vamos a asumir que estoy feliz con ellos y se centran en por qué no se extenderían a Bools).

CompuChip
fuente
31
¿Qué pasa con una función? void Flip(bool& Flag) { Flag=!Flag; }Acorta tu expresión larga.
Harper
72
this.dataSource.trackedObject.currentValue.booleanFlag ^= 1;
KamilCuk
13
se pueden asignar expresiones largas a referencias para simplificar el código
Quentin 2
66
@KamilCuk Esto podría funcionar, pero se mezclan los tipos. Usted asigna un número entero a un bool.
Harper
77
@ user463035818 hay *= -1, sin embargo, que por alguna razón me parece más intuitivo que ^= true.
CompuChip

Respuestas:

15

Esta pregunta es realmente interesante desde un punto de vista puramente teórico. Dejando a un lado si sería útil o no un operador de palanca booleano mutante o unario, o por qué muchos idiomas han optado por no proporcionar uno, me aventuré en una búsqueda para ver si realmente existe o no.

TL; DR aparentemente no, pero Swift le permite implementar uno. Si solo desea ver cómo se hace, puede desplazarse hasta el final de esta respuesta.


Después de una búsqueda (rápida) de las características de varios idiomas, me sentiría seguro al decir que ningún idioma ha implementado este operador como una operación in situ mutante estricta (corríjame si encuentra uno). Entonces, lo siguiente sería ver si hay idiomas que le permitan construir uno. Lo que esto requeriría es dos cosas:

  1. ser capaz de implementar operadores (unarios) con funciones
  2. permitiendo que dichas funciones tengan argumentos de paso por referencia (para que puedan mutar sus argumentos directamente)

Muchos idiomas se descartarán inmediatamente por no admitir ninguno de estos requisitos o ambos. Java for one no permite la sobrecarga de operadores (u operadores personalizados) y, además, todos los tipos primitivos se pasan por valor. Go no tiene soporte para la sobrecarga del operador (excepto por hacks ) en absoluto. Rust solo permite la sobrecarga del operador para tipos personalizados. Casi podría lograr esto en Scala , que le permite usar funciones con nombres muy creativos y también omitir paréntesis, pero lamentablemente no hay paso por referencia. Fortran se acerca mucho, ya que permite operadores personalizados, pero específicamente les prohíbe tener inout parámetros (que están permitidos en funciones normales y subrutinas).


Sin embargo, hay al menos un idioma que cumple todos los requisitos: Swift . Si bien algunas personas se han vinculado a la próxima función miembro .toggle () , también puede escribir su propio operador, que de hecho admite argumentos inout . Lo y he aquí:

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false
Lauri Piispanen
fuente
66
El lenguaje proporcionará esto: github.com/apple/swift-evolution/blob/master/proposals/…
Cristik
3
Sí, pero la pregunta específicamente pedía un operador , no una función miembro.
Lauri Piispanen
44
"Ni Rust" => Sí, lo hace
Boiethios
3
@Boiethios gracias! Editado para Rust: sin embargo, solo permite operadores sobrecargados para tipos personalizados, por lo que no hay dados.
Lauri Piispanen
3
@LauriPiispanen La regla es más general que eso, y debido a la regla huérfana , FYI
Boiethios
143

Alternar el bit booleano

... eso me permitiría abreviar a = !a sin repetir la expresión paraa ...

Este enfoque no es realmente un operador puro de "cambio mutante", pero cumple con los criterios anteriores; el lado derecho de la expresión no involucra la variable en sí.

Cualquier lenguaje con una asignación XOR booleana (por ejemplo ^=) permitiría cambiar el valor actual de una variable, por ejemplo a, mediante la asignación XOR a true:

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

Como señaló @cmaster en los comentarios a continuación, lo anterior supone que aes de tipo booly no, por ejemplo, un número entero o un puntero. Si, ade hecho, es otra cosa (por ejemplo, algo que no se boolevalúa como un valor "verdadero" o "falso", con una representación de bits que no es 0b1o 0b0, respectivamente), lo anterior no se cumple.

Para un ejemplo concreto, Java es un lenguaje donde está bien definido y no está sujeto a ninguna conversión silenciosa. Citando el comentario de @ Boann a continuación:

En Java, ^y ^=tienen un comportamiento explícitamente definido para booleanos y enteros ( 15.22.2. Los operadores lógicos booleanos &, ^y| ), donde cualquiera de los dos lados del operador deben ser booleanos, o en ambos lados deben ser enteros. No hay conversión silenciosa entre esos tipos. Por lo tanto, no funcionará silenciosamente si ase declara como un entero, sino que dará un error de compilación. Por a ^= true;lo tanto, es seguro y está bien definido en Java.


Rápido: toggle()

A partir de Swift 4.2, la siguiente propuesta de evolución ha sido aceptada e implementada:

Esto agrega una toggle()función nativa alBool tipo en Swift.

toggle()

Alterna el valor de la variable booleana.

Declaración

mutating func toggle()

Discusión

Utilice este método para cambiar un valor booleano truea falseo desde falsea true.

var bools = [true, false]

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

Este no es un operador, per se, pero permite un enfoque nativo de lenguaje para alternar booleano.

dfri
fuente
3
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Samuel Liew
44

En C ++ es posible cometer el pecado cardinal de redefinir el significado de los operadores. Con esto en mente, y un poco de ADL, todo lo que tenemos que hacer para desatar el caos en nuestra base de usuarios es esto:

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

Rendimiento esperado:

6
0
1
0
1
Richard Hodges
fuente
9
“El pecado capital de redefinir el significado de los operadores”: entiendo que exageraba, pero en realidad no es un pecado capital reasignar nuevos significados a los operadores existentes en general.
Konrad Rudolph
55
@KonradRudolph Lo que quiero decir con esto, por supuesto, es que un operador debe tener sentido lógico con respecto a su significado aritmético o lógico habitual. 'operator +' para 2 cadenas es lógico, ya que la concatenación es similar (en nuestra opinión) a la suma o acumulación. operator<<ha llegado a significar 'transmitido' debido a su abuso original en el STL. Naturalmente, no significará 'aplicar la función de transformación en el RHS', que es esencialmente para lo que la he reubicado aquí.
Richard Hodges
66
No creo que sea un buen argumento, lo siento. En primer lugar, la concatenación de cadenas tiene una semántica completamente diferente de la suma (¡ni siquiera es conmutativa!). En segundo lugar, según su lógica, <<es un operador de desplazamiento de bits, no un operador de "transmisión". El problema con su redefinición no es que sea inconsistente con los significados existentes del operador, sino que no agrega ningún valor sobre una simple llamada de función.
Konrad Rudolph
8
<<Parece una mala sobrecarga. Lo haría !invert= x;;)
Yakk - Adam Nevraumont
12
@ Yakk-AdamNevraumont LOL, ahora me tienes empezado: godbolt.org/z/KIkesp
Richard Hodges
37

Siempre que incluyamos lenguaje ensamblador ...

ADELANTE

INVERT para un complemento bit a bit.

0= para un complemento lógico (verdadero / falso).

DrSheldon
fuente
44
Discutible, en cada lenguaje orientado a la pila un unario notes un operador "alternar" :-)
Bergi
2
Claro, es un poco una respuesta lateral ya que el OP está claramente pensando en lenguajes tipo C que tienen una declaración de asignación tradicional. Creo que las respuestas laterales como esta tienen valor, aunque solo sea para mostrar una parte del mundo de la programación en la que el lector podría no haber pensado. ("Hay más lenguajes de programación en el cielo y en la tierra, Horacio, que los que soñamos en nuestra filosofía".)
Wayne Conrad
31

Disminuir un C99 booltendrá el efecto deseado, al igual que aumentar o disminuir los bittipos admitidos en algunos dialectos de microcontroladores pequeños (que por lo que he observado tratan los bits como campos de bits anchos de un solo bit, por lo que todos los números pares se truncan a 0 y todos números impares a 1). En particular, no recomendaría tal uso, en parte porque no soy un gran admirador de la boolsemántica de tipos [En mi humilde opinión, el tipo debería haber especificado que un boolcomportamiento al que se almacena cualquier valor distinto de 0 o 1 puede comportarse cuando se lee como si tiene un valor entero no especificado (no necesariamente consistente); si un programa está tratando de almacenar un valor entero que no se sabe que es 0 o 1, debería usarlo !!primero].

Super gato
fuente
2
C ++ hizo lo mismo con bool hasta C ++ 17 .
Davis Herring
31

Lenguaje ensamblador

NOT eax

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

Adrian
fuente
2
No creo que esto sea exactamente lo que opinaba, suponiendo que se trata de un ensamblaje x86. por ejemplo, en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA
8
Puede utilizar todos los bits en su lugar: NO 0x00000000 = 0xFFFFFFFF
Adrian
55
Bueno, sí, aunque estaba tratando de establecer la distinción entre operadores booleanos y operadores bit a bit. Como op dice en la pregunta, suponga que el lenguaje tiene un tipo booleano bien definido: "(entonces no hay estilo C int a = TRUE)".
BurnsBA
55
@ BurnsBA: de hecho, los lenguajes ensambladores no tienen un solo conjunto de valores de verdad / falsedad bien definidos. En code-golf, se ha discutido mucho sobre los valores que puede devolver para problemas booleanos en varios idiomas. Como asm no tiene una if(x)construcción , es totalmente razonable permitir 0 / -1 como falso / verdadero, como para comparar SIMD ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ). Pero tienes razón en que esto se rompe si lo usas en cualquier otro valor.
Peter Cordes
44
Es difícil tener una única representación booleana dado que PCMPEQW da como resultado 0 / -1, pero SETcc da como resultado 0 / + 1.
Eugene Styer
28

Supongo que no va a elegir un lenguaje basado únicamente en esto :-) En cualquier caso, puede hacer esto en C ++ con algo como:

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

Vea el siguiente programa completo, por ejemplo:

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

Esto produce, como se esperaba:

false
true
false
paxdiablo
fuente
2
¿Te gustaría return b = !btambién? Alguien leyendo foo = makenot(x) || yo simplemente un simple foo = makenot(bar)podría asumir que makenotera una función pura, y asumir que no había ningún efecto secundario. Enterrar un efecto secundario dentro de una expresión más grande es probablemente un mal estilo, y el único beneficio de un tipo de retorno no nulo es habilitarlo.
Peter Cordes
2
@MatteoItalia sugiere template<typename T> T& invert(T& t) { t = !t; return t; } que las plantillas se convertirán a / desde boolsi se usan en un boolobjeto no . IDK si eso es bueno o malo. Presumiblemente bueno.
Peter Cordes
21

Visual Basic.Net admite esto mediante un método de extensión.

Defina el método de extensión así:

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

Y luego llámalo así:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Entonces, su ejemplo original se vería así:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip
Azul Reginald
fuente
2
Si bien esto funciona como la abreviatura que buscaba el autor, por supuesto, debe usar dos operadores para implementar Flip(): =y Not. Es interesante saber que en el caso de llamar SomeInstance.SomeBoolean.Flipfunciona incluso si SomeBooleanes una propiedad, mientras que el código C # equivalente no se compilaría.
BACON
14

En Rust, puede crear su propio rasgo para extender los tipos que implementan el Notrasgo:

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

También puedes usar ^= true como se sugiere, y en el caso de Rust, no hay problema posible para hacer esto porque falseno es un número entero "disfrazado" como en C o C ++:

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

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

En python

Python admite dicha funcionalidad, si la variable tiene tipo bool (que es Verdadero o Falso) con el exclusive or (^=)operador:

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

En C # :

boolean.variable.down.here ^= true;

El operador booleano ^ es XOR, y XORing con verdadero es lo mismo que invertir.

rakensi
fuente