Có ngôn ngữ nào có toán tử chuyển đổi boolean unary không?


141

Vì vậy, đây là nhiều hơn một câu hỏi lý thuyết. C ++ và các ngôn ngữ (in) trực tiếp dựa trên nó (Java, C #, PHP) có các toán tử phím tắt để gán kết quả của hầu hết các toán tử nhị phân cho toán hạng đầu tiên, chẳng hạn như

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

nhưng khi tôi muốn chuyển đổi một biểu thức boolean, tôi luôn thấy mình viết một cái gì đó như

a = !a;

Điều này gây khó chịu khi amột biểu hiện dài như thế nào.

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

(vâng, Luật của Demeter, tôi biết).

Vì vậy, tôi đã tự hỏi, có ngôn ngữ nào với toán tử chuyển đổi boolean unary sẽ cho phép tôi viết tắt a = !amà không lặp lại biểu thức cho a, ví dụ

!=a;  
// or
a!!;

Giả sử rằng ngôn ngữ của chúng ta có loại boolean thích hợp (như booltrong C ++) và đó alà loại đó (vì vậy không có kiểu C int a = TRUE).

Nếu bạn có thể tìm thấy một nguồn tài liệu, tôi cũng muốn tìm hiểu xem, ví dụ như các nhà thiết kế C ++ đã xem xét việc thêm một toán tử như thế khi booltrở thành một loại tích hợp và nếu vậy, tại sao họ lại quyết định chống lại nó.


(Lưu ý: Tôi biết rằng một số người có quan điểm rằng việc chuyển nhượng không nên sử dụng =và điều đó +++=không phải là các nhà khai thác hữu ích nhưng lỗi trong thiết kế; chúng ta hãy chỉ giả sử tôi đang hạnh phúc với họ và tập trung vào lý do tại sao họ sẽ không mở rộng đến bools).


31
Điều gì về một chức năng void Flip(bool& Flag) { Flag=!Flag; }Việc rút ngắn biểu thức dài của bạn.
harper

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

13
các biểu thức dài có thể được chỉ định cho các tham chiếu để đơn giản hóa mã
Quentin 2

6
@KamilCuk Điều này có thể hoạt động, nhưng bạn trộn các loại. Bạn gán một số nguyên cho một bool.
harper

7
@ user463035818 có *= -1, mặc dù vậy, vì một số lý do tôi thấy trực quan hơn ^= true.
CompuChip

Câu trả lời:


15

Câu hỏi này thực sự thú vị từ quan điểm lý thuyết thuần túy. Đặt sang một bên có hay không một toán tử chuyển đổi boolean biến đổi có hữu ích hay không, hoặc tại sao nhiều ngôn ngữ đã chọn không cung cấp một ngôn ngữ, tôi mạo hiểm tìm kiếm xem liệu nó có thực sự tồn tại hay không.

TL; DR rõ ràng là không, nhưng Swift cho phép bạn thực hiện một. Nếu bạn chỉ muốn xem nó được thực hiện như thế nào, bạn có thể cuộn xuống cuối câu trả lời này.


Sau khi tìm kiếm (nhanh chóng) các tính năng của các ngôn ngữ khác nhau, tôi cảm thấy an toàn khi nói rằng không có ngôn ngữ nào triển khai toán tử này như một hoạt động đột biến tại chỗ nghiêm ngặt (hãy sửa tôi nếu bạn tìm thấy một ngôn ngữ). Vì vậy, điều tiếp theo sẽ là xem có ngôn ngữ nào cho phép bạn xây dựng một ngôn ngữ không. Điều này đòi hỏi hai điều:

  1. có thể thực hiện các toán tử (đơn nguyên) với các hàm
  2. cho phép các hàm nói có các đối số tham chiếu qua (để chúng có thể thay đổi trực tiếp các đối số của chúng)

Nhiều ngôn ngữ sẽ ngay lập tức bị loại trừ vì không hỗ trợ một trong hai hoặc cả hai yêu cầu này. Java cho một người không cho phép quá tải toán tử (hoặc toán tử tùy chỉnh) và ngoài ra, tất cả các kiểu nguyên thủy được truyền theo giá trị. Go không có hỗ trợ cho quá tải toán tử (ngoại trừ hack ). Rust chỉ cho phép người vận hành quá tải cho các loại tùy chỉnh. Bạn gần như có thể đạt được điều này trong Scala , cho phép bạn sử dụng các hàm được đặt tên rất sáng tạo và cũng bỏ qua dấu ngoặc đơn, nhưng thật đáng buồn là không có tham chiếu qua. Fortran được rất gần ở chỗ nó cho phép các nhà khai thác tùy chỉnh, nhưng đặc biệt cấm họ khỏi phải inout tham số (được phép trong các hàm và chương trình con bình thường).


Tuy nhiên, có ít nhất một ngôn ngữ đánh dấu vào tất cả các ô cần thiết: Swift . Trong khi một số người đã liên quan đến sắp tới .toggle () chức năng thành viên, bạn cũng có thể viết điều hành của riêng mình, mà thực sự hỗ trợ inout đối số. Lo và kìa:

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false

6
Ngôn ngữ sẽ cung cấp điều này: github.com/apple/swift-evolution/blob/master/proposeals/
mẹo

3
Có, nhưng câu hỏi đặc biệt yêu cầu một nhà điều hành , không phải là một chức năng thành viên.
Lauri Piispanen

4
"Không phải Rust" => Đúng vậy
Boiethios

3
@Boiethios cảm ơn! Đã chỉnh sửa cho Rust - chỉ cho phép các nhà khai thác quá tải cho các loại tùy chỉnh, do đó, không có xúc xắc.
Lauri Piispanen

3
@LauriPiispanen Quy tắc này chung chung hơn thế, và do quy tắc mồ côi , FYI
Boiethios

143

Vượt qua bit boolean

... Điều đó sẽ cho phép tôi viết tắt a = !a mà không cần lặp lại biểu thức choa ...

Cách tiếp cận này không thực sự là một toán tử "đột biến lật" thuần túy, nhưng thực hiện đầy đủ các tiêu chí của bạn ở trên; phía bên phải của biểu thức không liên quan đến chính biến.

Bất kỳ ngôn ngữ nào có gán XOR boolean (ví dụ ^=) sẽ cho phép lật giá trị hiện tại của một biến a, bằng cách gán XOR cho true:

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

Như @cmaster đã chỉ ra trong các bình luận bên dưới, các giả định ở trên athuộc loại boolchứ không phải là số nguyên hoặc con trỏ. Nếu atrên thực tế là một cái gì đó khác (ví dụ như một cái gì đó không boolđánh giá thành giá trị "trung thực" hoặc "giả mạo", với một đại diện bit không tương ứng 0b1hoặc 0b0, tương ứng), thì ở trên không giữ.

Đối với một ví dụ cụ thể, Java là một ngôn ngữ mà ngôn ngữ này được xác định rõ và không chịu bất kỳ chuyển đổi im lặng nào. Trích dẫn bình luận của @ Boann từ bên dưới:

Trong Java, ^^=đã xác định một cách rõ ràng hành vi cho các phép toán luận và cho số nguyên ( 15.22.2. Các nhà khai thác logic Boolean &, ^| ), nơi một trong hai cả hai bên của các nhà điều hành phải có các phép toán luận, hoặc cả hai bên phải là số nguyên. Không có chuyển đổi im lặng giữa các loại. Vì vậy, nó sẽ không âm thầm trục trặc nếu ađược khai báo là một số nguyên, mà thay vào đó, đưa ra một lỗi biên dịch. Vì vậy, a ^= true;là an toàn và được xác định rõ trong Java.


Nhanh: toggle()

Kể từ Swift 4.2, đề xuất tiến hóa sau đây đã được chấp nhận và thực hiện:

Điều này thêm một toggle()chức năng riêng cho Boolloại trong Swift.

toggle()

Chuyển giá trị của biến Boolean.

Tờ khai

mutating func toggle()

Thảo luận

Sử dụng phương pháp này để chuyển đổi giá trị Boolean từ truesang falsehoặc từ falseđến true.

var bools = [true, false]

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

Đây không phải là một toán tử, nhưng sẽ cho phép một cách tiếp cận ngôn ngữ riêng để chuyển đổi boolean.


3
Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Samuel Liew

44

Trong C ++, có thể cam kết Hồng y Sin xác định lại ý nghĩa của các toán tử. Với suy nghĩ này và một chút ADL, tất cả những gì chúng ta cần làm để giải phóng tình trạng hỗn loạn trên cơ sở người dùng của mình là:

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

sản lượng dự kiến:

6
0
1
0
1

9
Sau đó, tội lỗi của Hồng y xác định lại ý nghĩa của các nhà khai thác - Tôi hiểu rằng bạn đang phóng đại nhưng thực sự không phải là một tội lỗi chính yếu để gán lại ý nghĩa mới cho các nhà khai thác nói chung.
Konrad Rudolph

5
@KonradRudolph Ý tôi là điều này tất nhiên là một toán tử nên có ý nghĩa logic đối với ý nghĩa số học hoặc logic thông thường của nó. 'Toán tử + `cho 2 chuỗi là hợp lý, vì phép nối tương tự (trong mắt chúng ta) với phép cộng hoặc tích lũy. operator<<đã có nghĩa là 'phát trực tuyến' vì sự lạm dụng ban đầu của nó trong STL. Nó không tự nhiên có nghĩa là 'áp dụng chức năng biến đổi trên RHS', mà về cơ bản là những gì tôi đã tái sử dụng nó cho ở đây.
Richard Hodges

6
Tôi không nghĩ đó là một cuộc tranh luận tốt, xin lỗi. Trước hết, nối chuỗi có ngữ nghĩa hoàn toàn khác với bổ sung (nó thậm chí không giao hoán!). Thứ hai, theo logic của bạn <<là một toán tử dịch chuyển bit, không phải là một toán tử phát trực tiếp ra ngoài. Vấn đề với định nghĩa lại của bạn không phải là nó không phù hợp với ý nghĩa hiện có của toán tử, mà là nó không thêm giá trị nào trong một lệnh gọi hàm đơn giản.
Konrad Rudolph

8
<<có vẻ như quá tải xấu Tôi sẽ làm !invert= x;;)
Yakk - Adam Nevraumont

12
@ Yakk-AdamNevraumont LOL, bây giờ bạn đã bắt đầu với tôi: godbolt.org/z/KIkesp
Richard Hodges

37

Miễn là chúng tôi bao gồm ngôn ngữ lắp ráp ...

FORTH

INVERT cho một bổ sung bitwise.

0= cho một bổ sung hợp lý (đúng / sai).


4
Có thể cho rằng, trong mọi ngôn ngữ hướng ngăn xếp, một unary notlà toán tử "chuyển đổi" :-)
Bergi

2
Chắc chắn, đó là một câu trả lời ngang vì OP rõ ràng đang nghĩ về các ngôn ngữ giống như C có tuyên bố chuyển nhượng truyền thống. Tôi nghĩ rằng những câu trả lời ngang như thế này có giá trị, nếu chỉ để thể hiện một phần của thế giới lập trình mà người đọc có thể không nghĩ tới. ("Có nhiều ngôn ngữ lập trình trên trời và đất, Horatio, hơn là mơ ước trong triết lý của chúng tôi.")
Wayne Conrad

31

Việc giảm C99 boolsẽ có hiệu ứng mong muốn, cũng như tăng hoặc giảm các bitloại được hỗ trợ trong một số phương ngữ vi điều khiển nhỏ (mà từ đó tôi đã quan sát thấy các bit là bitfield rộng một bit, vì vậy tất cả các số chẵn đều bị cắt thành 0 và tất cả số lẻ thành 1). Tôi đặc biệt không khuyến nghị sử dụng như vậy, một phần vì tôi không phải là người hâm mộ lớn về boolngữ nghĩa loại [IMHO, loại nên đã chỉ định rằng một boolgiá trị nào khác 0 hoặc 1 được lưu trữ có thể hoạt động khi đọc như thể nó giữ một giá trị nguyên không xác định (không nhất thiết nhất quán); nếu một chương trình đang cố lưu trữ một giá trị số nguyên không được biết là 0 hoặc 1, thì nó sẽ sử dụng !!nó trước tiên].


2
C ++ đã làm điều tương tự bool cho đến khi C ++ 17 .
Davis Herring

31

2
Tôi không nghĩ rằng đây là những gì op có trong đầu, giả sử đây là lắp ráp x86. ví dụ: en.wikibooks.org/wiki/X86_Assugging/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA

8
Bạn có thể sử dụng tất cả các bit thay thế: KHÔNG 0x00000000 = 0xFFFFFFFF
Adrian

5
Vâng, vâng, mặc dù tôi đã cố gắng phân biệt sự khác biệt giữa các toán tử boolean và bitwise. Như op nói trong câu hỏi, giả sử ngôn ngữ có kiểu boolean được xác định rõ: "(vì vậy không có kiểu C int a = TRUE)."
BurnsBA

5
@BurnsBA: thực sự, các ngôn ngữ lắp ráp không có một tập hợp các giá trị trung thực / sai lệch được xác định rõ. Về môn đánh gôn, nhiều điều đã được thảo luận về những giá trị nào bạn được phép trả lại cho các vấn đề boolean bằng nhiều ngôn ngữ khác nhau. Vì asm không có if(x)cấu trúc , nên hoàn toàn hợp lý khi cho phép 0 / -1 là sai / đúng, như đối với so sánh SIMD ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ). Nhưng bạn đúng rằng điều này sẽ phá vỡ nếu bạn sử dụng nó trên bất kỳ giá trị nào khác.
Peter Cordes

4
Thật khó để có một đại diện Boolean duy nhất cho rằng PCMPEQW cho kết quả 0 / -1, nhưng SETcc cho kết quả 0 / + 1.
Eugene Styer

28

Tôi cho rằng bạn sẽ không chọn ngôn ngữ chỉ dựa trên điều này :-) Trong mọi trường hợp, bạn có thể thực hiện điều này trong C ++ với một cái gì đó như:

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

Xem chương trình hoàn chỉnh sau đây để biết ví dụ:

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

Kết quả này, như mong đợi:

false
true
false

2
Bạn có muốn return b = !bnhư vậy không? Ai đó đọc foo = makenot(x) || yhoặc chỉ đơn giản foo = makenot(bar)có thể cho rằng đó makenotlà một chức năng thuần túy, và cho rằng không có tác dụng phụ. Chôn một hiệu ứng phụ bên trong một biểu thức lớn hơn có lẽ là phong cách xấu và lợi ích duy nhất của kiểu trả về không trống là cho phép điều đó.
Peter Cordes

2
@MatteoItalia đề xuất template<typename T> T& invert(T& t) { t = !t; return t; } , được tạo khuôn mẫu sẽ chuyển đổi thành / từ boolnếu được sử dụng trên một boolđối tượng không . IDK nếu đó là tốt hay xấu. Có lẽ là tốt.
Peter Cordes


21

Visual Basic.Net hỗ trợ điều này thông qua một phương thức mở rộng.

Xác định phương thức mở rộng như vậy:

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

Và sau đó gọi nó như thế này:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Vì vậy, ví dụ ban đầu của bạn sẽ trông giống như:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip

2
Mặc dù điều này không hoạt động như tốc ký mà tác giả đang tìm kiếm, tất nhiên, bạn phải sử dụng hai toán tử để thực hiện Flip(): =Not. Thật thú vị khi biết rằng trong trường hợp gọi SomeInstance.SomeBoolean.Flipnó hoạt động ngay cả khi SomeBooleanlà một thuộc tính, trong khi mã C # tương đương sẽ không biên dịch.
BACON

14

Trong Rust, bạn có thể tạo đặc điểm của riêng mình để mở rộng các loại thực hiện Nottính trạng đó:

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

Bạn cũng có thể sử dụng ^= truenhư được đề xuất và trong trường hợp của Rust, không có vấn đề nào có thể xảy ra vì đây falsekhông phải là số nguyên "được ngụy trang" như trong C hoặ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

Trong C # :

boolean.variable.down.here ^= true;

Toán tử boolean ^ là XOR và XORing với true cũng giống như đảo ngược.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.