ตัวดำเนินการกำหนดสายที่ขาดหายไป

สมมติว่าคุณมีประเภทสำหรับพิกัดคาร์ทีเซียน

 struct Point { x : f64 , y : f64 }

ตอนนี้เราต้องการเพิ่มวิธีการหมุน มีสองรูปแบบทั่วไปที่นี่:

 impl Point { // Return a new point, don't modify the self parameter. fn rotate_new ( & self , rad : f64 ) -> Point ; // Modify self. fn rotate_self ( & mut self , rad : f64 ); }

ทั้งสองอย่างนี้ค่อนข้างสมเหตุสมผล แต่ทั้งคู่ก็ไม่สะดวกในบางสถานการณ์

ตัวอย่างเช่น กับตัวแปร rotate_new :

 // Creating a new value is easy. turn_to ( angle .rotate_new ( amount )); // But modifying a variable creates duplication. angle = angle .rotate_new ( amount );

และสำหรับตัวแปร rotate_self :

 // Creating a new value is very annoying. let mut target = angle .clone (); target .rotate_self ( amount ); turn_to ( target ); // But modifying a variable is natural. angle .rotate_self ( amount );

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

แล้วเราจะกำหนดอินเทอร์เฟซที่ตอบสนองทุกกรณีการใช้งานได้อย่างไร? ทางออกที่ง่ายที่สุดคือการกำหนดทั้งสองอย่างที่เราทำข้างต้น อย่างไรก็ตาม อาจมีรูปแบบการตั้งชื่อที่ดีกว่า rotate และหมุน with_rotation rotate และ rotate_new อย่างไร หรือเราจะเรียกพวกมันว่าตัว rotate และ rotated ตัวได้น่ารัก ? ใช้งานได้ แต่ต้องใช้โค้ดซ้ำๆ มากมายในอินเทอร์เฟซ นอกจากนี้ การขาดแบบแผนการตั้งชื่อที่รัดกุมทำให้ผู้ใช้ไลบรารีเดาว่าเมธอดนี้เรียกว่าอะไร

มีทางเลือกอื่นอีกไหม? ปรากฎว่าภาษาโปรแกรมได้แก้ปัญหานี้ไปแล้ว! คิดถึงตัวดำเนินการ +

 turn ( angle + adjustment ); angle += adjustment ;

โอเค ไม่ใช่แค่ตัวดำเนินการ + แต่ยังรวมถึงตัวดำเนินการ += ด้วย ด้วยสองสิ่งนี้เราสามารถจัดการทั้งสองกรณีได้อย่างเป็นธรรมชาติ! โชคดีสำหรับเรา ไม่ใช่แค่ + ที่มี += ขึ้นอยู่กับภาษาของคุณ คุณอาจมีตัวดำเนินการ op-assign สำหรับตัวดำเนินการส่วนใหญ่ในภาษาของคุณ! -= , <<= , ||=

แต่มันเป็น (โดยปกติ) รายการตัวดำเนินการคงที่ซึ่งมีความหมายโดยนัย ผู้ใช้ของคุณอาจไม่ยินดีหากคุณใช้ << มากเกินไปเพื่อหมุนจุดเพียงเพราะคุณต้องการใช้ประโยชน์จาก <<= !

จะเกิดอะไรขึ้นถ้าเราสามารถมีโอเปอเรเตอร์ op-assign สำหรับการเรียกใช้ฟังก์ชันได้ ตัวดำเนินการกำหนดการโทร ไวยากรณ์จะต้องแตกต่างออกไปเล็กน้อยเพราะเราต้องใส่ชื่อฟังก์ชันลงไป แต่ฉันคิดว่าเราสามารถหาบางอย่างที่ดูไม่แปลกเกินไป

 angle .= rotate ( amount ); angle .= rotate ( amount ); angle .rotate = ( amount );

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

ที่เดียวที่ฉันคิดว่าสิ่งนี้มีประโยชน์คือ รูปแบบตัวสร้าง รูปแบบตัวสร้างเป็นวิธีจำลองคีย์เวิร์ดและอาร์กิวเมนต์ทางเลือกในภาษาที่ไม่มี

 let http = reqwest :: Client :: builder () .connect_timeout ( std :: time :: Duration :: from_secs ( 1 )) .timeout ( std :: time :: Duration :: from_secs ( 10 )) .build () ? ;

โดยส่วนตัวแล้วฉันพบว่ารูปแบบนี้น่ารำคาญ มันใช้งานได้ดีใน “เส้นทางแห่งความสุข” แต่จะพังทลายลงหากคุณจำเป็นต้องตั้งค่าตัวเลือกในบางครั้ง

 let mut builder = reqwest :: Client :: builder () .connect_timeout ( std :: time :: Duration :: from_secs ( 1 )) .timeout ( std :: time :: Duration :: from_secs ( 10 )); if cfg .use_rustls { builder = builder .use_rustls_tls (); } let http = builder .build () ? ;

เมื่อคุณเจอสิ่งนี้—โดยที่คุณมีเงื่อนไขหรือต้องการมอบหมายตัวเลือกบางอย่างให้กับฟังก์ชันอื่น—ฉันพบว่าวิธีการที่ไม่แน่นอนอย่างง่าย ( & mut self ) จะจบลงง่ายและสอดคล้องกันมากขึ้น

 // Assuming the API was changed. let mut builder = reqwest :: Client :: builder (); builder .connect_timeout ( std :: time :: Duration :: from_secs ( 1 )) builder .timeout ( std :: time :: Duration :: from_secs ( 10 )); if cfg .use_rustls { builder .use_rustls_tls (); } configure_client ( & mut builder ); let http = builder .build () ? ;

ฉันคิดว่า call-assign ทำให้สิ่งนี้ทำงานได้ดีกับ API ที่มีอยู่ ซึ่งหมายความว่าผู้ใช้ “เส้นทางแห่งความสุข” สามารถใช้การโยงต่อไปได้หากต้องการ แต่คนอย่างฉันสามารถมอบหมายงานง่ายๆ ได้

 // Assuming the API was changed. let mut builder = reqwest :: Client :: builder (); builder .connect_timeout = ( std :: time :: Duration :: from_secs ( 1 )) builder .timeout = ( std :: time :: Duration :: from_secs ( 10 )); if cfg .use_rustls { builder .use_rustls_tls = (); } configure_client ( & mut builder ); let http = builder .build () ? ;

(ด้วยเหตุผลด้านประสิทธิภาพ การโอเวอร์โหลดการกำหนดการเรียกนั้นเป็นเรื่องที่สมเหตุสมผล เพื่อให้สามารถใช้งานได้อย่างมีประสิทธิภาพมากขึ้น แต่นั่นเป็นเพียงการปรับให้เหมาะสมเท่านั้น)

มีภาษาใดบ้างที่สนับสนุนสิ่งนี้ พวกเขามีกลไกในการเพิ่มประสิทธิภาพการใช้งาน call และ call-assign แยกจากกันหรือไม่?