การปิดด้วยผลข้างเคียงถูกพิจารณาว่าเป็น "ลักษณะการทำงาน" หรือไม่?


9

ภาษาโปรแกรมสมัยใหม่จำนวนมากสนับสนุนแนวคิดเรื่องการปิดบางอย่างเช่นโค้ด (บล็อกหรือฟังก์ชัน) ที่

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

นี่คือตัวอย่างของการปิดที่เขียนใน Scala:

def filterList(xs: List[Int], lowerBound: Int): List[Int] =
  xs.filter(x => x >= lowerBound)

ฟังก์ชั่นตัวอักษรx => x >= lowerBoundมีตัวแปรอิสระlowerBoundซึ่งถูกปิด (ผูกพัน) โดยอาร์กิวเมนต์ของฟังก์ชั่นfilterListที่มีชื่อเดียวกัน การปิดจะถูกส่งไปยังวิธีการของไลบรารีfilterซึ่งสามารถเรียกใช้ซ้ำได้ตามฟังก์ชั่นปกติ

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

คำนิยามของฟังก์ชั่นการเขียนโปรแกรมบนวิกิพีเดียอ่าน:

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

และเพิ่มเติมเกี่ยวกับ

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

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

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

การใช้การปิดด้วยผลข้างเคียงนี้ถือเป็นลักษณะการใช้งานหรือเป็นการปิดซึ่งเป็นการสร้างทั่วไปที่สามารถใช้งานได้ทั้งการใช้งานและการเขียนโปรแกรมแบบไม่ทำงาน? มีวรรณกรรมในหัวข้อนี้บ้างไหม?

โน๊ตสำคัญ

ฉันไม่ได้ถามถึงประโยชน์ของผลข้างเคียงหรือการปิดด้วยผลข้างเคียง นอกจากนี้ฉันไม่สนใจในการอภิปรายเกี่ยวกับข้อดี / ข้อเสียของการปิดโดยมีหรือไม่มีผลข้างเคียง

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


3
ในสไตล์ / ภาษาที่ใช้งานได้จริงผลข้างเคียงเป็นไปไม่ได้ ... ดังนั้นฉันคิดว่ามันจะเป็นเรื่องของ: ในระดับใดที่ความบริสุทธิ์หนึ่งจะพิจารณารหัสที่จะทำงาน?
Steven Evers

@SnOrfus: ที่ 100%?
จอร์โจ

1
ผลข้างเคียงเป็นไปไม่ได้ในภาษาที่ใช้งานได้จริงดังนั้นควรตอบคำถามของคุณ
Steven Evers

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

ผู้ลงคะแนนสามารถฝากข้อความถึงวิธีการปรับปรุงคำถามนี้ได้หรือไม่?
Giorgio

คำตอบ:


7

ไม่มี นิยามของกระบวนทัศน์การทำงานนั้นเกี่ยวกับการขาดสถานะและการขาดผลข้างเคียงโดยปริยาย มันไม่เกี่ยวกับฟังก์ชั่นระดับสูง, การปิด, การจัดการรายการที่รองรับภาษาหรือคุณสมบัติภาษาอื่น ๆ ...

ชื่อของฟังก์ชั่นการเขียนโปรแกรมมาจากความคิดทางคณิตศาสตร์ของฟังก์ชั่น - การโทรซ้ำ ๆ บนอินพุตเดียวกันจะให้ผลลัพธ์เหมือนกัน - ฟังก์ชั่น nullipotent นี้สามารถทำได้ถ้าข้อมูลไม่เปลี่ยนรูป เพื่อความสะดวกในการพัฒนาฟังก์ชั่นกลายเป็นไม่แน่นอน (ฟังก์ชั่นการเปลี่ยนแปลงข้อมูลยังไม่เปลี่ยนรูป) และทำให้ความคิดของฟังก์ชั่นการสั่งซื้อที่สูงขึ้น (functionals ในคณิตศาสตร์เป็นอนุพันธ์ตัวอย่างเช่น) - ฟังก์ชั่น สำหรับความเป็นไปได้ของฟังก์ชั่นที่จะต้องดำเนินการรอบ ๆ และส่งผ่านเป็นอาร์กิวเมนต์ฟังก์ชันชั้นหนึ่งถูกนำมาใช้; ดังต่อไปนี้เพื่อส่งเสริมการผลิต, การปิดปรากฏ

แน่นอนว่านี่เป็นมุมมองที่ง่ายมาก


3
ฟังก์ชั่นที่สูงกว่าไม่มีความเกี่ยวข้องใด ๆ กับความไม่แน่นอนและฟังก์ชั่นชั้นหนึ่ง ฉันสามารถผ่านฟังก์ชั่นและสร้างฟังก์ชั่นอื่น ๆ จากพวกเขาใน Haskell ได้อย่างง่ายดายโดยไม่ต้องมีสภาวะที่ไม่แน่นอนและผลข้างเคียง
tdammers

@ ผู้ส่งอีเมลฉันอาจไม่ได้แสดงความชัดเจนมาก เมื่อฉันกล่าวว่าฟังก์ชั่นกลายเป็นไม่แน่นอนฉันไม่ได้อ้างถึงความไม่แน่นอนของข้อมูล แต่ความจริงที่ว่าพฤติกรรมของฟังก์ชั่นสามารถเปลี่ยนแปลงได้ (โดยฟังก์ชั่นลำดับสูง)
m3th0dman

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

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

@Giorgio: ที่สุดของภาษา FP คลาสสิกที่ทำมีความไม่แน่นอน; Haskell เป็นคนเดียวที่ฉันสามารถนึกถึงส่วนบนของหัวที่ไม่อนุญาตให้มีการเปลี่ยนแปลงเลย ถึงกระนั้นการหลีกเลี่ยงสถานะที่ไม่แน่นอนเป็นค่าที่สำคัญใน FP
tdammers

9

ไม่ "ฟังก์ชั่นสไตล์" หมายถึงการเขียนโปรแกรมโดยไม่มีผลข้างเคียง

หากต้องการดูสาเหตุให้ดูที่บล็อกของ Eric Lippertเกี่ยวกับForEach<T>วิธีการขยายและทำไม Microsoft ไม่ได้รวมวิธีการเรียงลำดับไว้ในLinq :

ฉันต่อต้านปรัชญาในการให้วิธีการดังกล่าวด้วยเหตุผลสองประการ

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

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

foreach(Foo foo in foos){ statement involving foo; }

เป็นรหัสนี้:

foos.ForEach((Foo foo)=>{ statement involving foo; });

ซึ่งใช้อักขระเดียวกันเกือบทั้งหมดในลำดับที่แตกต่างกันเล็กน้อย และรุ่นที่สองนั้นยากที่จะเข้าใจยากที่จะทำการดีบั๊กและแนะนำซีแมนทิกส์ปิดซึ่งอาจเปลี่ยนอายุการใช้งานของวัตถุในรูปแบบที่บอบบาง


1
แม้ว่าผมเห็นด้วยกับเขา ... ParallelQuery<T>.ForAll(...)ข้อโต้แย้งของอ่อนตัวเล็กน้อยเมื่อพิจารณา การดำเนินการดังกล่าวIEnumerable<T>.ForEach(...)จะเป็นประโยชน์อย่างยิ่งสำหรับการแก้จุดบกพร่องForAllงบ (แทนที่ForAllด้วยForEachและลบAsParallel()และคุณสามารถมากได้ง่ายขึ้นผ่านขั้นตอน / แก้ปัญหาได้)
สตีเว่น Evers

เห็นได้ชัดว่าโจดัฟฟี่มีความคิดที่แตกต่าง : D
Robert Harvey

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

5
@Giorgio: การปิดไม่จำเป็นต้องบริสุทธิ์เพื่อยังถือว่าปิด พวกเขาจะต้องบริสุทธิ์ แต่ต้องได้รับการพิจารณาว่าเป็น "สไตล์การทำงาน"
Robert Harvey

3
@ จอร์โจ: มันมีประโยชน์มากที่จะสามารถปิดล้อมรอบรัฐที่ไม่แน่นอนและผลข้างเคียงและส่งผ่านไปยังฟังก์ชั่นอื่น มันตรงกันข้ามกับเป้าหมายของการเขียนโปรแกรมการทำงาน ฉันคิดว่าความสับสนจำนวนมากมาจากการสนับสนุนภาษาที่หรูหราสำหรับลูกแกะที่มีอยู่ทั่วไปในภาษาที่ใช้งานได้
GlenPeterson

0

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

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

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

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

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