ปัญหาการใช้งานการปิดในการตั้งค่าที่ไม่ทำงาน


18

ในภาษาการเขียนโปรแกรมการปิดเป็นคุณสมบัติที่นิยมและมักต้องการ Wikipediaพูดว่า (เหมืองที่เน้น):

ในวิทยาการคอมพิวเตอร์การปิด (... ) คือฟังก์ชั่นร่วมกับสภาพแวดล้อมอ้างอิงสำหรับตัวแปรที่ไม่ใช่ท้องถิ่นของฟังก์ชั่นนั้น การปิดช่วยให้ฟังก์ชั่นเข้าถึงตัวแปรนอกขอบเขตศัพท์ได้ทันที

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

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

นอกจากนี้ดูเหมือนว่าจะมีอุปสรรคสำคัญเมื่อดำเนินการปิด สิ่งที่น่าสังเกตมากที่สุดคือ Java 7 น่าจะรวมเอาไว้ แต่ฟีเจอร์ดังกล่าวจะถูกส่งกลับไปยังรีลีสในอนาคต

เหตุใดการปิดจึงยากที่จะเข้าใจ คำถามนี้กว้างเกินไปและคลุมเครือดังนั้นขอให้ฉันมุ่งเน้นไปที่คำถามที่เชื่อมต่อกันเหล่านี้มากขึ้น:

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

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


คำถามที่ดี. มีการนำ Closures มาใช้ใน Scala และ Martin Odersky เขียนคอมไพเลอร์ Java 1.5 ดังนั้นจึงไม่ชัดเจนว่าทำไมพวกเขาถึงไม่อยู่ใน Java 7 C # มีพวกเขาอยู่ (ฉันจะพยายามเขียนคำตอบที่ดีกว่าในภายหลัง)
Dave Clarke

4
ภาษาที่ใช้งานไม่ได้อย่าง Lisp และ ML รองรับการปิดได้ดีดังนั้นจึงไม่มีเหตุผลทางอรรถศาสตร์ที่แท้จริงสำหรับพวกเขาที่จะเป็นปัญหา
Gilles 'หยุดความชั่วร้าย'

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

1
ลองดูที่pdfs.semanticscholar.org/73a2/ … - ผู้เขียน Lua ทำให้มันเป็นวิธีที่ฉลาดมากและอภิปรายปัญหาทั่วไปของการปิดการใช้งานเช่นกัน
Bulat

คำตอบ:


10

ฉันขอให้คุณไปที่หน้าวิกิพีเดียปัญหา Funarg ได้ไหม? อย่างน้อยนี่เป็นวิธีที่คนคอมไพเลอร์ใช้อ้างอิงปัญหาการปิดตัวลง

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

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

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

กรณีแรก (ลงล่าง funargs) ไม่ยากที่จะใช้และสามารถพบได้ในภาษาขั้นตอนเก่ากว่าเช่น Algol, C และ Pascal กระโปรงชนิด C เป็นปัญหาเนื่องจากมันไม่อนุญาตให้มีฟังก์ชั่นที่ซ้อนกัน แต่ Algol และ Pascal ทำบัญชีที่จำเป็นเพื่อให้ฟังก์ชั่นภายในเพื่ออ้างอิงตัวแปรสแต็คของฟังก์ชั่นด้านนอก

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


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

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


1
คุณรู้จักภาษาใดบ้างที่แยกความแตกต่างของคดีขึ้นและลง? ใน. NET ภาษาวิธีทั่วไปที่คาดว่าจะได้รับฟังก์ชั่นลดลงอย่างเดียวสามารถรับโครงสร้างประเภททั่วไปพร้อมกับผู้รับมอบสิทธิ์ที่จะได้รับโครงสร้างเช่น byref (ใน C #, " refพารามิเตอร์") ถ้าผู้เรียก encapsulated ตัวแปรทั้งหมดที่น่าสนใจในโครงสร้างผู้รับมอบสิทธิ์อาจเป็นแบบสแตติกทั้งหมดหลีกเลี่ยงความต้องการการจัดสรรฮีปใด ๆ คอมไพเลอร์ไม่เสนอความช่วยเหลือด้านไวยากรณ์ที่ดีสำหรับการสร้างดังกล่าว แต่ Framework สามารถรองรับได้
supercat

2
@supercat: Rust มีประเภทการปิดหลายแบบที่ให้คุณบังคับใช้ในเวลาคอมไพล์ถ้าฟังก์ชันภายในจำเป็นต้องใช้ heap อย่างไรก็ตามนี่ไม่ได้หมายความว่าการใช้งานไม่สามารถพยายามหลีกเลี่ยงการจัดสรรฮีปโดยไม่บังคับให้คุณสนใจประเภทพิเศษเหล่านั้นทั้งหมด คอมไพเลอร์สามารถลองอนุมานอายุการใช้งานของฟังก์ชันหรือสามารถใช้การตรวจสอบรันไทม์เพื่อบันทึกตัวแปรลงใน heap เมื่อจำเป็นเท่านั้น (ดูที่หัวข้อ "ขอบเขตคำศัพท์" ของวิวัฒนาการกระดาษLuaเพื่อดูรายละเอียด)
hugomg

5

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

ลองใช้ pseudo-C # ต่อไปนี้ (ฉันตัดเนื้อหาบางส่วนของ C # ออกเล็กน้อย):

int x = 1;
function f = function() { x++; };
for (int i = 1; i < 10; i++) {
    f();
}
print x; // Should print 9

คอมไพเลอร์แปลงสิ่งนี้ให้เป็นสิ่งนี้:

class FunctionStuff {
   int x;
   void theFunction() {
       x++;
   }
}

FunctionStuff theClosureObject = new FunctionStuff();
theClosureObject.x = 1;
for (int i = 1; i < 10; i++) {
    theClosureObject.theFunction();
}
print theClosureObject.x; // Should print 9

(ในความเป็นจริงตัวแปร f จะยังคงถูกสร้างขึ้นโดยที่ f คือ 'ตัวแทน' (= ตัวชี้ฟังก์ชั่น) แต่ผู้รับมอบสิทธิ์นี้ยังคงเกี่ยวข้องกับวัตถุ theClosureObject - ฉันออกส่วนนี้เพื่อความชัดเจนสำหรับผู้ที่ไม่คุ้นเคย ด้วย C #)

การเปลี่ยนแปลงครั้งนี้มีขนาดค่อนข้างใหญ่และยุ่งยาก: พิจารณาการปิดภายในการปิดและการมีส่วนร่วมของการปิดด้วยคุณลักษณะส่วนที่เหลือของภาษา C # ฉันสามารถจินตนาการได้ว่าฟีเจอร์นั้นถูกผลักกลับไปที่ Java เนื่องจาก Java 7 มีฟีเจอร์ใหม่ ๆ อยู่มากมาย


ฉันสามารถดูว่าสิ่งนี้จะไป; การปิดหลายครั้งและการเข้าถึงขอบเขตหลักของตัวแปรเดียวกันนั้นจะยุ่งเหยิง
Raphael

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

@ ราฟาเอล: คุณรู้สึกอย่างไรเกี่ยวกับการปิดภายในการปิด แขวนไว้ให้ฉันเพิ่มสิ่งต่อไปนี้
Alex ten Brink

5

เพื่อตอบคำถามของคุณ พิธีการที่อธิบายโดยMorrisett และ Harperครอบคลุมความหมายขนาดใหญ่และขั้นตอนเล็ก ๆ ของภาษา polymorphic ลำดับสูงที่มีการปิด มีเอกสารก่อนหน้านี้ที่ให้ความหมายประเภทที่คุณกำลังมองหา ดูตัวอย่างเช่นที่เครื่อง SECD การเพิ่มการอ้างอิงที่ไม่แน่นอนหรือคนในท้องถิ่นที่ไม่แน่นอนลงในความหมายเหล่านี้เป็นเรื่องง่าย ฉันไม่เห็นว่ามีปัญหาทางเทคนิคใด ๆ ในการให้ความหมายเช่นนั้น


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

1
@ ราฟาเอล: อาจจะมีคนที่เรียบง่ายกว่า ฉันจะพยายามหาบางอย่างและกลับไปหาคุณ ในกรณีใด ๆ รูปที่ 8 มีความหมายที่คุณกำลังมองหา
Dave Clarke

บางทีคุณสามารถให้ภาพรวมคร่าวๆ ความคิดหลักในคำตอบของคุณ?
ราฟาเอล

2
@Raphael บางทีฉันอาจแนะนำคุณไปยังบันทึกการบรรยายของฉันฉันใช้สำหรับหลักสูตรภาษาการเขียนโปรแกรมซึ่งให้คำแนะนำอย่างรวดเร็ว โปรดค้นหาเอกสารประกอบคำบรรยาย 8 และ 9
Uday Reddy

1
ลิงค์นั้นปรากฏว่าไม่ผ่านการตรวจสอบหรือไม่สามารถตรวจสอบได้ ( cs.cmu.edu/afs/cs/user/rwh/public/www/home/papers/gcpoly/tr.pdf ) ฉันได้รับอนุญาต 403
Ben Fletcher
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.