ในฟังก์ชั่นการเขียนโปรแกรมตัวแปรที่ผันแปรได้ในท้องถิ่นโดยไม่มีผลข้างเคียงใด ๆ ที่ยังถือว่าเป็น“ การปฏิบัติที่ไม่ดี”?


23

มีตัวแปรท้องถิ่นที่ไม่แน่นอนในฟังก์ชั่นที่ใช้ภายในเท่านั้น (เช่นฟังก์ชั่นไม่มีผลข้างเคียงอย่างน้อยก็ไม่ได้ตั้งใจ) ยังถือว่าเป็น "ไม่ทำงาน"?

เช่นในการตรวจสอบรูปแบบหลักสูตร "ฟังก์ชั่นการทำงานกับ Scala" จะถือว่าvarการใช้งานไม่ถูกต้อง

คำถามของฉันหากฟังก์ชั่นไม่มีผลข้างเคียงการเขียนโค้ดสไตล์ที่จำเป็นยังคงหมดกำลังใจอยู่หรือ?

เช่นแทนที่จะใช้การเรียกซ้ำหางที่มีรูปแบบสะสมมีอะไรผิดปกติกับการทำท้องถิ่นสำหรับวงและการสร้างท้องถิ่นที่ไม่แน่นอนListBufferและเพิ่มไปตราบใดที่การป้อนข้อมูลที่ไม่ได้มีการเปลี่ยนแปลงอย่างไร

หากคำตอบคือ "ใช่พวกเขาจะหมดกำลังใจเสมอแม้ว่าจะไม่มีผลข้างเคียง" แล้วอะไรคือเหตุผล?


3
คำแนะนำทั้งหมดคำแนะนำและอื่น ๆ เกี่ยวกับหัวข้อที่ฉันเคยได้ยินอ้างถึงสถานะที่ไม่แน่นอนที่ใช้ร่วมกันเป็นแหล่งของความซับซ้อน หลักสูตรนี้มีวัตถุประสงค์เพื่อให้ผู้เริ่มต้นใช้งานเท่านั้นหรือไม่ จากนั้นก็อาจเป็นการใช้งานเกินขนาดโดยเจตนาอย่างตั้งใจ
Kilian Foth

3
@KilianFoth: สถานะที่ไม่แน่นอนที่ใช้ร่วมกันเป็นปัญหาในบริบทที่มีหลายเธรด แต่สถานะที่ไม่แน่นอนที่ไม่แบ่งปันสามารถนำไปสู่โปรแกรมที่ยากต่อการให้เหตุผลเช่นกัน
Michael Shaw

1
ฉันคิดว่าการใช้ตัวแปรที่เปลี่ยนแปลงได้ในท้องถิ่นนั้นไม่จำเป็นต้องเป็นการฝึกฝนที่ไม่ดี แต่ไม่ใช่ "รูปแบบการทำงาน": ฉันคิดว่าจุดประสงค์ของหลักสูตร Scala (ซึ่งฉันใช้ในช่วงฤดูใบไม้ร่วงที่ผ่านมา) คือการสอนให้คุณเขียนโปรแกรม เมื่อคุณสามารถแยกแยะความแตกต่างระหว่างสไตล์การใช้งานและรูปแบบที่จำเป็นได้อย่างชัดเจนคุณสามารถตัดสินใจได้ว่าจะใช้เมื่อใด varไม่สามารถใช้งานได้เสมอ Scala มี vals ขี้เกียจและการเพิ่มประสิทธิภาพการเรียกซ้ำหางซึ่งช่วยให้หลีกเลี่ยง vars อย่างสมบูรณ์
Giorgio

คำตอบ:


17

สิ่งหนึ่งที่เป็นการปฏิบัติที่ไม่ดีอย่างไม่น่าเชื่อที่นี่คือการอ้างว่าบางสิ่งบางอย่างเป็นฟังก์ชั่นที่บริสุทธิ์เมื่อมันไม่ได้

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

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


16

ปัญหาไม่ได้แปรปรวนต่อการขาดความโปร่งใสในการอ้างอิง

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

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

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


4
จุดของคุณเกี่ยวกับการบันทึกความจำดีมาก โปรดทราบว่า Haskell ให้ความสำคัญอย่างยิ่งกับความโปร่งใสในการเขียนโปรแกรมสำหรับอ้างอิง แต่พฤติกรรมการบันทึกเช่นเดียวกับการประเมินผลที่ขี้เกียจนั้นเกี่ยวข้องกับการกลายพันธุ์ที่เกิดขึ้นโดยภาษารันไทม์ที่อยู่เบื้องหลัง
CA McCann

@CA McCann: ฉันคิดว่าสิ่งที่คุณพูดมีความสำคัญมาก: ในภาษาที่ใช้งานได้จริงรันไทม์สามารถใช้การกลายพันธุ์เพื่อเพิ่มประสิทธิภาพการคำนวณ แต่ไม่มีการสร้างในภาษาที่อนุญาตให้โปรแกรมเมอร์ใช้การกลายพันธุ์ อีกตัวอย่างหนึ่งคือ while loop พร้อมกับตัวแปร loop: ใน Haskell คุณสามารถเขียนฟังก์ชัน recursive แบบหางที่อาจนำมาใช้กับตัวแปรที่ไม่แน่นอน (เพื่อหลีกเลี่ยงการใช้ stack) แต่สิ่งที่โปรแกรมเมอร์เห็นคือข้อโต้แย้งฟังก์ชันที่ไม่เปลี่ยนรูปที่ถูกส่งจาก โทรไปยังถัดไป
Giorgio

@Michael Shaw: +1 สำหรับ "ปัญหาไม่ใช่ความไม่แน่นอนต่อกัน แต่ขาดความโปร่งใสในการอ้างอิง" บางทีคุณสามารถอ้างอิงภาษา Clean ที่คุณมีประเภทที่ไม่ซ้ำกัน: สิ่งเหล่านี้อนุญาตให้เปลี่ยนแปลงได้ แต่ยังคงรับประกันความโปร่งใสในการอ้างอิง
Giorgio

@Giorgio: ฉันไม่รู้อะไรเลยเกี่ยวกับ Clean ถึงแม้ว่าฉันเคยได้ยินมันพูดถึงเป็นครั้งคราว บางทีฉันควรดูมัน
Michael Shaw

@Michael Shaw: ฉันไม่รู้เกี่ยวกับ Clean มากนัก แต่ฉันรู้ว่ามันใช้ชนิดที่เป็นเอกลักษณ์เพื่อความโปร่งใสในการอ้างอิง โดยทั่วไปคุณสามารถแก้ไขวัตถุข้อมูลโดยมีเงื่อนไขว่าหลังจากการดัดแปลงคุณไม่มีการอ้างอิงถึงค่าเก่า IMO สิ่งนี้แสดงให้เห็นถึงจุดของคุณ: ความโปร่งใสในการอ้างอิงเป็นจุดที่สำคัญที่สุดและการเปลี่ยนแปลงไม่ได้เป็นวิธีเดียวที่จะทำให้มั่นใจได้
Giorgio

8

มันไม่ดีเลยที่จะทำให้เรื่องนี้กลายเป็น "แนวปฏิบัติที่ดี" และ "แนวปฏิบัติที่ไม่ดี" สกาล่าสนับสนุนค่าที่ไม่แน่นอนเพราะพวกเขาแก้ปัญหาบางอย่างได้ดีกว่าค่าที่ไม่เปลี่ยนรูปแบบนั่นคือพวกที่ซ้ำในธรรมชาติ

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

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

เมื่อคำนึงถึงปัญหามากมายที่โดยทั่วไปต้องใช้ตัวแปรที่ไม่แน่นอน (เช่นการวนซ้ำ) สามารถแก้ไขได้ดีขึ้นด้วยฟังก์ชันลำดับที่สูงขึ้นซึ่งภาษาเช่น Scala ให้ (แผนที่ / ตัวกรอง / การพับ) ระวังสิ่งเหล่านั้น


2
ใช่ฉันแทบไม่ต้องห่วงเมื่อใช้คอลเลกชันของสกาล่า map, filter, foldLeftและforEach ทำเคล็ดลับมากที่สุดของเวลา แต่เมื่อพวกเขาไม่ได้มีความสามารถในการรู้สึกฉัน "OK" เพื่อกลับไปแรงเดรัจฉานรหัสความจำเป็นเป็นสิ่งที่ดี (ตราบใดที่ไม่มีผลข้างเคียงแน่นอน)
Eran Medan

3

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

คุณยังมีตัวเลือกเพิ่มเติมมากมายสำหรับการประมวลผลคอนเทนเนอร์ที่ใช้งานได้มากกว่าด้วยลูปที่จำเป็น ด้วยความจำเป็นคุณโดยทั่วไปมีfor, whileและการเปลี่ยนแปลงเล็กน้อยในทั้งสองชอบและdo...whileforeach

ในการทำงานคุณจะมีการรวม, นับ, กรอง, ค้นหา, flatMap, fold, groupBy, lastIndexWhere, แผนที่, maxBy, minBy, พาร์ติชัน, สแกน, sortBy, sortWith, span และ takeWhile เพียงแค่ตั้งชื่อทั่วไปของ Scala ห้องสมุดมาตรฐาน เมื่อคุณคุ้นเคยกับการมีอยู่ของforลูปที่จำเป็นดูเหมือนว่าพื้นฐานเกินไปในการเปรียบเทียบ

เหตุผลที่แท้จริงเพียงข้อเดียวในการใช้ความไม่แน่นอนของท้องถิ่นนั้นมีประสิทธิภาพเป็นครั้งคราว


2

ฉันจะบอกว่ามันโอเคเป็นส่วนใหญ่ ยิ่งไปกว่านั้นการสร้างโครงสร้างด้วยวิธีนี้อาจเป็นวิธีที่ดีในการปรับปรุงการแสดงในบางกรณี Clojure ได้ takled ปัญหานี้โดยการให้Transient โครงสร้างข้อมูล

แนวคิดพื้นฐานคือการอนุญาตให้การกลายพันธุ์ในท้องถิ่นอยู่ในขอบเขตที่ จำกัด จากนั้นจึงตรึงโครงสร้างก่อนที่จะส่งคืน ด้วยวิธีนี้ผู้ใช้ของคุณยังสามารถให้เหตุผลเกี่ยวกับรหัสของคุณราวกับว่ามันบริสุทธิ์ แต่คุณสามารถทำการเปลี่ยนแปลงในสถานที่เมื่อคุณต้องการ

เป็นลิงค์พูดว่า:

ถ้าต้นไม้ตกลงไปในป่ามันจะส่งเสียงหรือไม่? หากฟังก์ชั่นแท้ๆแปลงข้อมูลท้องถิ่นบางอย่างเพื่อสร้างค่าตอบแทนที่ไม่เปลี่ยนรูปแบบนั่นเป็นเรื่องปกติไหม


2

การไม่มีตัวแปรที่เปลี่ยนแปลงได้แบบโลคอลจะมีข้อดีเพียงข้อเดียว - ทำให้ฟังก์ชั่นเป็นมิตรต่อเธรด

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

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