ภาษาใดบ้างที่มีโอเปอเรเตอร์การสลับบูลีนแบบเอกนารี?


141

นี่เป็นคำถามเชิงทฤษฎีมากกว่า 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;

(ใช่กฎหมายของ Demeter ฉันรู้)

ดังนั้นฉันจึงสงสัยว่ามีภาษาใดบ้างที่มีโอเปอเรเตอร์การสลับบูลีนแบบเอกนารีที่จะทำให้ฉันสามารถใช้ตัวย่อa = !aโดยไม่ต้องทำซ้ำการแสดงออกaเช่น

!=a;  
// or
a!!;

สมมติว่าภาษาของเรามีประเภทบูลีนที่เหมาะสม (เช่นboolใน C ++) และaเป็นประเภทนั้น (ดังนั้นจึงไม่มีรูปแบบ C int a = TRUE)

หากคุณสามารถหาแหล่งข้อมูลที่เป็นเอกสารได้ฉันก็สนใจที่จะเรียนรู้ว่าเช่นนักออกแบบ C ++ ได้พิจารณาเพิ่มผู้ดำเนินการเช่นนั้นเมื่อboolกลายเป็นประเภทในตัวหรือไม่และถ้าเป็นเช่นนั้นทำไมพวกเขาถึงเลือกมัน


(หมายเหตุ: ฉันรู้ว่าบางคนมีความเห็นว่าการกำหนดไม่ควรใช้ =และ++และ+=ไม่ได้เป็นผู้ประกอบการที่มีประโยชน์ แต่ข้อบกพร่องการออกแบบให้เพียงสมมติผมมีความสุขกับพวกเขาและมุ่งเน้นไปที่ว่าทำไมพวกเขาจะไม่ขยายไป bools)


31
ฟังก์ชั่นอะไรที่ทำให้void Flip(bool& Flag) { Flag=!Flag; }การแสดงออกของคุณสั้นลง
ฮาร์เปอร์

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

13
นิพจน์แบบยาวสามารถกำหนดให้กับการอ้างอิงเพื่อทำให้โค้ดง่ายขึ้น
เควนติน 2

6
@KamilCuk สิ่งนี้อาจใช้งานได้ แต่คุณรวมประเภทการผสม คุณกำหนดจำนวนเต็มให้กับบูล
ฮาร์เปอร์

7
@ user463035818 มีแต่ซึ่งด้วยเหตุผลบางอย่างที่ฉันพบว่าใช้งานง่ายกว่า*= -1 ^= true
CompuChip

คำตอบ:


15

คำถามนี้น่าสนใจจริง ๆ จากมุมมองทางทฤษฎีล้วนๆ การตั้งค่าว่าตัวดำเนินการสลับบูลีนที่ไม่น่าจะกลายพันธุ์นั้นมีประโยชน์หรือไม่หรือทำไมหลาย ๆ ภาษาเลือกที่จะไม่ใช้ภาษาเดียว

TL; DRดูเหมือนไม่ แต่ Swift ให้คุณใช้งานได้ หากคุณเพียงต้องการดูว่าเสร็จแล้วคุณสามารถเลื่อนไปที่ด้านล่างของคำตอบนี้


หลังจากการค้นหา (อย่างรวดเร็ว) ไปยังคุณลักษณะของภาษาต่างๆฉันรู้สึกปลอดภัยที่จะพูดว่าไม่มีภาษาใดที่ใช้ตัวดำเนินการนี้เป็นการดำเนินการแบบแทนที่อย่างเคร่งครัด (แก้ไขให้ถูกต้องถ้าคุณพบเจอ) ดังนั้นสิ่งต่อไปคือดูว่ามีภาษาที่ให้คุณสร้างหรือไม่ สิ่งนี้จะต้องมีสองสิ่ง:

  1. ความสามารถในการใช้ตัวดำเนินการ (unary) พร้อมกับฟังก์ชั่น
  2. การอนุญาตให้ฟังก์ชั่นที่กล่าวมามีข้อโต้แย้งแบบ pass-by-reference (เพื่อให้พวกเขาสามารถเปลี่ยนแปลงข้อโต้แย้งได้โดยตรง)

หลายภาษาจะถูกตัดออกทันทีเนื่องจากไม่สนับสนุนข้อกำหนดเหล่านี้อย่างใดอย่างหนึ่งหรือทั้งสองอย่าง Javaสำหรับหนึ่งไม่อนุญาตให้ผู้ประกอบการมากไป (หรือผู้ประกอบการที่กำหนดเอง) และนอกจากนี้ประเภทดั้งเดิมทั้งหมดจะถูกส่งผ่านโดยค่า ไปไม่มีการสนับสนุนสำหรับผู้ประกอบการมากไป (ยกเว้นแฮ็ก ) ใด ๆ สนิมอนุญาตให้ผู้ปฏิบัติงานโอเวอร์โหลดสำหรับประเภทที่กำหนดเองเท่านั้น คุณเกือบจะได้สิ่งนี้ในScalaซึ่งให้คุณใช้ฟังก์ชั่นที่ตั้งชื่ออย่างสร้างสรรค์และไม่ต้องใส่วงเล็บ แต่น่าเศร้าที่ไม่มีการอ้างอิงแบบผ่าน ๆ Fortranได้รับอย่างใกล้ชิดในการที่จะช่วยให้ผู้ประกอบการที่กำหนดเอง แต่เฉพาะห้ามพวกเขาจากการมีInOut พารามิเตอร์ (ซึ่งได้รับอนุญาตในฟังก์ชั่นและรูทีนย่อยปกติ)


มี แต่อย่างน้อยหนึ่งภาษาที่เห็บกล่องทั้งหมดที่จำเป็น: สวิฟท์ ในขณะที่บางคนเชื่อมโยงกับฟังก์ชั่นสมาชิก. สลับ () ที่กำลังจะเกิดขึ้นคุณยังสามารถเขียนโอเปอเรเตอร์ของคุณเองซึ่งรองรับอาร์กิวเมนต์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
"Neither does Rust" => ใช่แล้ว
Boiethios

3
@Boiethios ขอบคุณ! แก้ไขสำหรับสนิม - อนุญาตเฉพาะโอเปอร์เรเตอร์ที่โอเวอร์โหลดสำหรับประเภทที่กำหนดเองเท่านั้นดังนั้นจึงไม่มีลูกเต๋า
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การประเมินค่าเป็น "ความจริง" หรือ "เท็จ" ด้วยการเป็นตัวแทนบิตที่ไม่ได้0b1หรือ0b0ตามลำดับ) ดังกล่าวข้างต้นไม่ได้ถือ

สำหรับตัวอย่างที่เป็นรูปธรรม Java เป็นภาษาที่มีการกำหนดชัดเจนและไม่ต้องมีการแปลงใด ๆ แสดงความคิดเห็น @ Boann จากด้านล่าง:

ใน Java ^และ^=มีพฤติกรรมที่กำหนดไว้อย่างชัดเจนสำหรับ booleans และสำหรับจำนวนเต็ม ( 15.22.2. ผู้ประกอบการตรรกะบูลีน&, ^และ| ) ที่ทั้งสองฝ่ายอย่างใดอย่างหนึ่งของผู้ประกอบการจะต้อง booleans หรือทั้งสองฝ่ายจะต้องเป็นจำนวนเต็ม ไม่มีการแปลงเงียบระหว่างประเภทเหล่านั้น ดังนั้นมันจะไม่ทำงานผิดปกติอย่างเงียบ ๆ หากaมีการประกาศเป็นจำนวนเต็ม แต่ให้ข้อผิดพลาดในการคอมไพล์ ดังนั้นa ^= true;มีความปลอดภัยและมีกำหนดในชวา


สวิฟท์: toggle()

ตั้งแต่ Swift 4.2 ข้อเสนอวิวัฒนาการต่อไปนี้ได้รับการยอมรับและนำไปใช้แล้ว:

นี่เป็นการเพิ่มtoggle()ฟังก์ชันเนทิฟให้กับBoolชนิดใน Swift

toggle()

สลับค่าของตัวแปรบูลีน

การประกาศ

mutating func toggle()

อภิปรายผล

ใช้วิธีนี้เพื่อสลับค่าบูลีนจากtrueไปfalseหรือจากไปfalsetrue

var bools = [true, false]

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

นี่ไม่ใช่โอเปอเรเตอร์ต่อ แต่จะอนุญาตให้มีวิธีดั้งเดิมในภาษาสำหรับการสลับบูลีน


3
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Samuel Liew

44

ใน C ++ เป็นไปได้ที่จะคอมมิชชัน Sin ของการนิยามความหมายของตัวดำเนินการอีกครั้ง ด้วยความคิดนี้และ 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
“ บาปที่สำคัญของการนิยามความหมายของผู้ประกอบการใหม่” - ฉันเข้าใจว่าคุณพูดเกินจริง แต่ไม่ใช่บาปที่สำคัญที่กำหนดความหมายใหม่ให้กับผู้ประกอบการที่มีอยู่โดยทั่วไป
Konrad Rudolph

5
@ KonradRudolph สิ่งที่ฉันหมายถึงโดยนี้แน่นอนว่าผู้ประกอบการควรสมเหตุสมผลด้วยเหตุผลทางคณิตศาสตร์หรือความหมายตรรกะ 'โอเปอเรเตอร์ + `สำหรับ 2 สายเป็นตรรกะเนื่องจากการต่อข้อมูลจะคล้ายกัน (ในใจของเรา) เพื่อเพิ่มหรือสะสม operator<<มาเพื่อหมายถึง 'สตรีม' เนื่องจากการละเมิดใน STL มันจะไม่ได้หมายความว่า 'ใช้ฟังก์ชั่นการแปลงกับ RHS' ซึ่งเป็นสิ่งที่ฉันได้นำมาใช้ใหม่ที่นี่
Richard Hodges

6
ฉันไม่คิดว่ามันเป็นข้อโต้แย้งที่ดีขอโทษ ก่อนอื่นการต่อข้อมูลสตริงมีความหมายที่แตกต่างอย่างสิ้นเชิงจากการเพิ่ม (มันไม่ได้เป็นแม้แต่การสลับ!) ประการที่สองโดยตรรกะของคุณ<<เป็นตัวดำเนินการเปลี่ยนบิตไม่ใช่ตัวดำเนินการ“ สตรีม” ปัญหาของการนิยามใหม่ของคุณไม่ใช่ว่ามันไม่สอดคล้องกับความหมายที่มีอยู่ของผู้ประกอบการ แต่มันจะไม่เพิ่มมูลค่าใด ๆ ผ่านการเรียกใช้ฟังก์ชันอย่างง่าย
Konrad Rudolph

8
<<ดูเหมือนว่าเกินพิกัดที่ไม่ดี ฉันจะทำ!invert= x;;)
Yakk - Adam Nevraumont

12
@ Yakk-AdamNevraumont ฮ่า ๆ ตอนนี้คุณได้เริ่มแล้ว: godbolt.org/z/KIkesp
Richard Hodges

37

ตราบใดที่เรารวมภาษาแอสเซมบลี ...

FORTH

INVERT สำหรับส่วนประกอบ bitwise

0= สำหรับส่วนเติมเต็มตรรกะ (จริง / เท็จ)


4
สามารถพิสูจน์ได้ในทุกภาษาที่เน้นการกอง unary เป็นตัวดำเนินnotการ "toggle" :-)
Bergi

2
แน่นอนว่ามันเป็นคำตอบด้านข้างเล็กน้อยเนื่องจาก OP คิดอย่างชัดเจนเกี่ยวกับภาษา C ที่มีคำสั่งมอบหมายแบบดั้งเดิม ฉันคิดว่าคำตอบด้านข้างเช่นนี้มีค่าถ้าเพียงเพื่อแสดงส่วนหนึ่งของโลกการเขียนโปรแกรมที่ผู้อ่านอาจไม่ได้คิด ("มีภาษาการเขียนโปรแกรมมากมายในสวรรค์และโลก Horatio มากกว่าความฝันในปรัชญาของเรา")
เวย์นคอนราด

31

การลดลงของ C99 boolจะมีผลตามที่ต้องการเช่นเดียวกับการเพิ่มหรือลดbitประเภทที่รองรับในภาษาท้องถิ่นตัวจิ๋ว - ไมโครคอนโทรลเลอร์ (ซึ่งจากสิ่งที่ฉันสังเกตเห็นว่าบิตเป็นบิตฟิลด์กว้างบิตเดียวดังนั้นตัวเลขทั้งหมดจึงถูกตัดเป็น 0 และทั้งหมด จำนวนคี่เป็น 1) ฉันจะไม่แนะนำการใช้งานดังกล่าวโดยเฉพาะอย่างยิ่งส่วนหนึ่งเป็นเพราะฉันไม่ใช่แฟนตัวยงของซีแมนทิกส์boolประเภท [IMHO ประเภทนั้นควรระบุว่า a boolซึ่งค่าอื่นใดที่ไม่ใช่ 0 หรือ 1 ถูกเก็บไว้อาจทำงานเมื่ออ่านราวกับ มันถือค่าจำนวนเต็มไม่ระบุ (ไม่จำเป็นต้องสอดคล้อง); หากโปรแกรมพยายามที่จะเก็บค่าจำนวนเต็มที่ไม่ทราบว่าเป็น 0 หรือ 1 มันควรจะใช้!!มันก่อน]


2
C ++ ทำสิ่งเดียวกันกับ bool จนกว่า C
Davis Herring

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-style int a = TRUE)"
BurnsBA

5
@BurnsBA: แน่นอนภาษาแอสเซมบลีไม่มีชุดของค่าความจริง / เท็จที่กำหนดไว้ดี เกี่ยวกับ code-golf มีการถกเถียงกันมากมายเกี่ยวกับคุณค่าที่คุณได้รับอนุญาตให้กลับมาสำหรับปัญหาบูลีนในภาษาต่างๆ เนื่องจาก asm ไม่มีif(x)โครงสร้างมันจึงสมเหตุสมผลที่จะอนุญาตให้ 0 / -1 เป็นเท็จ / จริงเช่นเปรียบเทียบ SIMD ( felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html ) แต่คุณคิดถูกว่านี่จะแตกถ้าคุณใช้มันกับค่าอื่น ๆ
Peter Cordes

4
เป็นการยากที่จะมีการเป็นตัวแทนบูลีนเดียวเนื่องจาก PCMPEQW ให้ผลลัพธ์เป็น 0 / -1 แต่ SETcc ให้ผลลัพธ์เป็น 0 / + 1
Eugene Styer

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เป็นฟังก์ชั่นที่บริสุทธิ์และถือว่าไม่มีผลข้างเคียง การฝังผลข้างเคียงภายในนิพจน์ที่มีขนาดใหญ่ขึ้นอาจเป็นรูปแบบที่ไม่ดีและประโยชน์เพียงอย่างเดียวของประเภทการส่งคืนที่ไม่ใช่โมฆะคือการเปิดใช้งานนั้น
Peter Cordes

2
@MatteoItalia แนะนำ template<typename T> T& invert(T& t) { t = !t; return t; }ว่า templated ใดที่จะแปลงเป็น / จากboolหากใช้กับboolวัตถุที่ไม่ใช่ IDK ถ้าดีหรือไม่ดี น่าจะดี
Peter Cordes

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ตามที่แนะนำและในกรณีของสนิมไม่มีปัญหาที่เป็นไปได้ที่จะทำเช่นนี้เพราะ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);
}


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.