อะไรคือความแตกต่างระหว่าง 'การปิด' และ 'แลมบ์ดา'?


811

มีคนอธิบายได้ไหม ฉันเข้าใจแนวคิดพื้นฐานที่อยู่เบื้องหลังพวกเขา แต่ฉันมักจะเห็นพวกเขาใช้สลับกันได้และฉันสับสน

และตอนนี้เราอยู่ที่นี่พวกเขาแตกต่างจากฟังก์ชั่นปกติอย่างไร


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



สำหรับการปิด PHP, ดูphp.net/manual/en/class.closure.php ไม่ใช่สิ่งที่โปรแกรมเมอร์ JavaScript คาดหวัง
PaulH

คำตอบของ SasQ นั้นยอดเยี่ยม IMHO คำถามนี้จะเป็นประโยชน์ต่อผู้ใช้ SO มากขึ้นหากนำผู้ชมไปยังคำตอบนั้น
AmigoNico

คำตอบ:


703

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

ปิดฟังก์ชั่นใด ๆ ที่ปิดมากกว่าสภาพแวดล้อมที่มันถูกกำหนดไว้ ซึ่งหมายความว่ามันสามารถเข้าถึงตัวแปรที่ไม่ได้อยู่ในรายการพารามิเตอร์ ตัวอย่าง:

def func(): return h
def anotherfunc(h):
   return func()

สิ่งนี้จะทำให้เกิดข้อผิดพลาดเนื่องจากfuncไม่ได้ปิดสภาพแวดล้อมในanotherfunc- hไม่ได้กำหนด funcปิดเฉพาะในสภาพแวดล้อมระดับโลกเท่านั้น สิ่งนี้จะได้ผล:

def anotherfunc(h):
    def func(): return h
    return func()

เนื่องจากที่นี่funcมีการกำหนดไว้ในanotherfuncและในไพ ธ อน 2.3 และสูงกว่า (หรือบางอย่างเช่นนี้) เมื่อพวกเขาเกือบจะปิดถูกต้อง (การกลายพันธุ์ยังไม่ทำงาน) นี่หมายความว่ามันปิด anotherfuncสภาวะแวดล้อมและสามารถเข้าถึงตัวแปรภายใน มัน. ในหลาม 3.1 ขึ้นไปการกลายพันธุ์ทำงานมากเกินไปเมื่อใช้คำหลักnonlocal

อีกจุดที่สำคัญ - funcจะยังคงปิดเหนือของสภาพแวดล้อมที่แม้ว่าจะไม่ถูกประเมินในanotherfunc anotherfuncรหัสนี้จะใช้งานได้:

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

สิ่งนี้จะพิมพ์ 10

สิ่งนี้ตามที่คุณสังเกตเห็นไม่มีอะไรเกี่ยวข้องกับแลมบ์ดา - มันเป็นแนวคิดที่แตกต่างกันสองแบบ (แม้ว่าเกี่ยวข้องกัน)


1
Claudiu เพื่อความรู้ที่ไม่แน่นอนของฉันหลามไม่เคยมีการปิดถูกต้อง พวกเขาแก้ไขปัญหาความไม่แน่นอนในขณะที่ฉันไม่ได้มองเหรอ? เป็นไปได้มาก ...
simon

simon - ถูกต้องพวกมันยังไม่ถูกต้อง ฉันคิดว่าหลาม 3.0 จะเพิ่มแฮ็คเพื่อแก้ไขปัญหานี้ (บางอย่างเช่นตัวระบุ 'nonlocal') ฉันจะเปลี่ยนคำตอบของฉันเพื่อสะท้อนสิ่งนั้น
Claudiu

2
@AlexanderOrlov: พวกเขาทั้ง lambdas และ closures Java ปิดตัวลงก่อนหน้านี้โดยใช้คลาสภายในที่ไม่ระบุชื่อ ในตอนนี้ฟังก์ชั่นดังกล่าวได้รับการทำให้ง่ายขึ้นทาง syntactically ผ่านการแสดงออกแลมบ์ดา ดังนั้นคุณลักษณะที่เกี่ยวข้องมากที่สุดของคุณลักษณะใหม่คือตอนนี้มีลูกแกะ มันไม่ถูกต้องที่จะเรียกพวกมันว่า lambdas พวกมันเป็น lambdas แน่นอน ทำไมผู้เขียน Java 8 อาจเลือกที่จะไม่เน้นข้อเท็จจริงที่ว่าพวกเขาเป็นคนปิดไม่ใช่สิ่งที่ฉันรู้
Claudiu

2
@AlexanderOrlov เนื่องจาก Java 8 lambdas ไม่ใช่การปิดที่แท้จริงพวกเขาจำลองการปิด พวกมันคล้ายกับ Python 2.3 closures มากขึ้น (ไม่มีความไม่แน่นอนดังนั้นข้อกำหนดสำหรับตัวแปรที่อ้างถึงว่า 'มีประสิทธิภาพขั้นสุดท้าย') และรวบรวมภายในถึงฟังก์ชั่น non-closure ที่ใช้ตัวแปรทั้งหมดที่อ้างอิงในขอบเขตการล้อมรอบเป็นพารามิเตอร์ที่ซ่อนอยู่
รถกระบะโลแกน

7
@Claudiu ฉันคิดว่าการอ้างอิงถึงการใช้งานภาษาเฉพาะ (Python) อาจทำให้คำตอบซับซ้อนเกินไป คำถามนี้ไม่เชื่อเรื่องภาษาเลย (รวมถึงไม่มีแท็กเฉพาะภาษา)
Matthew

318

มีความสับสนรอบ ๆ lambdas และ closures แม้ในคำตอบของคำถาม StackOverflow ที่นี่ แทนที่จะถามผู้เขียนโปรแกรมแบบสุ่มที่เรียนรู้เกี่ยวกับการปิดจากการฝึกด้วยภาษาการเขียนโปรแกรมบางอย่างหรือโปรแกรมเมอร์ clueless อื่น ๆ ให้เดินทางไปที่แหล่งที่มา (ที่ซึ่งมันเริ่มทั้งหมด) และเนื่องจาก lambdas และ closures มาจากแลมบ์ดาแคลคูลัสที่คิดค้นโดย Alonzo Church ย้อนกลับไปในยุค 30 ก่อนคอมพิวเตอร์อิเล็กทรอนิกส์เครื่องแรกแม้จะมีอยู่นี่คือที่มาผมพูดถึง

แลมบ์ดาแคลคูลัสเป็นภาษาการเขียนโปรแกรมที่ง่ายที่สุดในโลก สิ่งเดียวที่คุณทำได้: ►

  • แอปพลิเคชัน: ใช้นิพจน์หนึ่งกับอีกนิพจน์แสดงแทน f xการใช้การแสดงออกอย่างใดอย่างหนึ่งไปยังอีกที่แสดง
    (คิดว่าเป็นการเรียกใช้ฟังก์ชันซึ่งfเป็นฟังก์ชันและxเป็นพารามิเตอร์เท่านั้น)
  • บทคัดย่อ: ผูกสัญลักษณ์ที่เกิดขึ้นในนิพจน์เพื่อทำเครื่องหมายว่าสัญลักษณ์นี้เป็นเพียง "สล็อต" ช่องว่างที่รอเติมค่าคือ "ตัวแปร" เหมือนเดิม มันทำโดยการเพิ่มตัวอักษรกรีกλ(แลมบ์ดา) จากนั้นชื่อสัญลักษณ์ (เช่นx) จากนั้นเป็นจุด.ก่อนนิพจน์ นี้แล้วแปลงการแสดงออกเป็นฟังก์ชั่นคาดหวังว่าหนึ่งพารามิเตอร์
    ตัวอย่างเช่นλx.x+2ใช้การแสดงออกx+2และบอกว่าสัญลักษณ์xในการแสดงออกนี้เป็นตัวแปรที่ถูกผูกไว้ - มันสามารถถูกแทนที่ด้วยค่าที่คุณให้เป็นพารามิเตอร์
    โปรดทราบว่าฟังก์ชั่นที่กำหนดด้วยวิธีนี้ไม่ระบุชื่อ - มันไม่มีชื่อดังนั้นคุณยังไม่สามารถอ้างถึงได้ แต่คุณสามารถทำได้ทันทีโทรมัน (จำแอปพลิเค?) (λx.x+2) 7โดยการจัดหามันพารามิเตอร์มันกำลังรอการเช่นนี้ จากนั้นนิพจน์ (ในกรณีนี้คือค่าตามตัวอักษร) 7จะถูกแทนที่เช่นเดียวกับxในนิพจน์ย่อยx+2ของแลมบ์ดาที่ใช้ดังนั้นคุณจะได้รับ7+2ซึ่งจะลดลง9ตามกฎ arithmetics ทั่วไป

ดังนั้นเราจึงได้ไขปริศนาหนึ่งอย่าง:
แลมบ์ดาเป็นฟังก์ชั่นนิรนามจากตัวอย่างด้านบน, λx.x+2.


ในภาษาโปรแกรมต่าง ๆ ไวยากรณ์สำหรับฟังก์ชั่นนามธรรม (แลมบ์ดา) อาจแตกต่างกัน ตัวอย่างเช่นใน JavaScript ดูเหมือนว่านี้:

function(x) { return x+2; }

และคุณสามารถนำไปใช้กับพารามิเตอร์แบบนี้ได้ทันที:

(function(x) { return x+2; })(7)

หรือคุณสามารถเก็บฟังก์ชันนิรนาม (แลมบ์ดา) ไว้ในตัวแปรบางตัวได้:

var f = function(x) { return x+2; }

ซึ่งให้ชื่ออย่างมีประสิทธิภาพช่วยfให้คุณสามารถอ้างอิงและเรียกมันหลายครั้งในภายหลังเช่น:

alert(  f(7) + f(10)  );   // should print 21 in the message box

แต่คุณไม่ต้องตั้งชื่อมัน คุณสามารถเรียกมันได้ทันที:

alert(  function(x) { return x+2; } (7)  );  // should print 9 in the message box

ใน LISP เนื้อแกะจะทำแบบนี้:

(lambda (x) (+ x 2))

และคุณสามารถเรียกแลมบ์ดาดังกล่าวได้โดยใช้พารามิเตอร์กับพารามิเตอร์ทันที:

(  (lambda (x) (+ x 2))  7  )


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

ขณะที่ผมกล่าวว่าสิ่งที่เป็นนามธรรมแลมบ์ดาไม่เป็นผลผูกพันสัญลักษณ์ใน subexpression ของมันเพื่อให้มันกลายเป็น substitutible พารามิเตอร์ สัญลักษณ์ดังกล่าวเรียกว่าผูกพัน แต่ถ้ามีสัญลักษณ์อื่นในการแสดงออก ตัวอย่างเช่นλx.x/y+2. ในการแสดงออกนี้สัญลักษณ์xถูกผูกไว้โดยสิ่งที่เป็นนามธรรมแลมบ์ดาλx.ก่อนมัน แต่สัญลักษณ์อื่น ๆy, ไม่ผูกพัน - มันเป็นฟรี เราไม่ทราบว่ามันคืออะไรและที่มันมาจากเราจึงไม่ทราบว่ามันหมายถึงอะไรและเห็นคุณค่าของมันหมายถึงและดังนั้นเราจึงไม่สามารถประเมินการแสดงออกที่จนกว่าเราจะคิดออกว่าyหมายถึง

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

คุณสามารถคิดของฟรีสัญลักษณ์ตามที่กำหนดไว้ที่อื่นนอกแสดงออกใน "บริบทโดยรอบ" ซึ่งเรียกว่าสภาพแวดล้อม สภาพแวดล้อมอาจเป็นนิพจน์ที่ใหญ่กว่าซึ่งการแสดงออกนี้เป็นส่วนหนึ่งของ (ตามที่ Qui-Gon Jinn กล่าวว่า: "มีปลาที่ใหญ่กว่าเสมอ";)) หรือในห้องสมุดบางแห่งหรือในภาษา ( ดั้งเดิม )

สิ่งนี้ช่วยให้เราแบ่งการแสดงออกแลมบ์ดาออกเป็นสองประเภท:

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

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

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

ตัวอย่างเช่นหากคุณมีการแสดงออกแลมบ์ดาต่อไปนี้: λx.x/y+2สัญลักษณ์xถูกผูกไว้ในขณะที่สัญลักษณ์yนั้นว่างดังนั้นการแสดงออกจึงเป็นopenและไม่สามารถประเมินได้เว้นแต่คุณจะพูดว่าyหมายถึงอะไร(และเหมือนกันกับ+และ2ซึ่งฟรี) แต่สมมติว่าคุณมีสภาพแวดล้อมเช่นนี้:

{  y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5  }

นี้สภาพแวดล้อมวัสดุสิ้นเปลืองคำจำกัดความสำหรับทุกคน "ไม่ได้กำหนด" (ฟรี) สัญลักษณ์จากการแสดงออกแลมบ์ดาของเรา ( y, +, 2) และสัญลักษณ์พิเศษหลาย ( q, w) สัญลักษณ์ที่เราจำเป็นต้องกำหนดเป็นส่วนย่อยของสภาพแวดล้อม:

{  y: 3,
+: [built-in addition],
2: [built-in number]  }

และนี่เป็นการปิดการแสดงออกแลมบ์ดาของเราอย่างแม่นยำ:>

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


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

เอาล่ะตลาดองค์กรของ Sun / Oracle, Microsoft, Google ฯลฯ นั้นจะต้องถูกตำหนิเพราะนั่นคือสิ่งที่พวกเขาเรียกว่าโครงสร้างเหล่านี้ในภาษาของพวกเขา (Java, C #, Go ฯลฯ ) พวกเขามักจะเรียกว่า "การปิด" สิ่งที่ควรจะเป็นเพียงแค่ลูกแกะ หรือพวกเขาเรียกว่า "การปิด" เทคนิคเฉพาะที่พวกเขาใช้ในการกำหนดขอบเขตศัพท์ซึ่งก็คือความจริงที่ว่าฟังก์ชั่นสามารถเข้าถึงตัวแปรที่กำหนดไว้ในขอบเขตด้านนอกในเวลาที่คำจำกัดความของมัน พวกเขามักจะพูดว่าฟังก์ชั่น "ล้อม" ตัวแปรเหล่านี้นั่นคือจับพวกเขาลงในโครงสร้างข้อมูลบางอย่างเพื่อบันทึกพวกเขาจากการถูกทำลายหลังจากฟังก์ชั่นด้านนอกเสร็จสิ้นการดำเนินการ แต่นี่เป็นเพียงการแต่งหน้าโพสต์ factum "คติชนวิทยา" และการตลาดซึ่งจะทำให้สิ่งที่สับสนมากขึ้น

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

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

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

Closure {
   [pointer to the lambda function's machine code],
   [pointer to the lambda function's environment]
}

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

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

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

ฉันหวังว่าจะตอบคำถามของคุณ แต่ถ้าคุณมีคำถามติดตามอย่าลังเลที่จะถามพวกเขาในความคิดเห็นและฉันจะพยายามอธิบายให้ดีขึ้น


50
คำตอบที่ดีที่สุดในการอธิบายสิ่งต่าง ๆ โดยทั่วไปมากกว่าภาษาเฉพาะ
Shishir Arora

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

1
@SasQ> การปิดแลมบ์ดานิพจน์เป็นส่วนย่อยของคำจำกัดความในสภาพแวดล้อมที่ให้ค่ากับตัวแปรอิสระที่มีอยู่ในนิพจน์แลมบ์ดานั้น <ดังนั้นในโครงสร้างข้อมูลตัวอย่างของคุณนั่นหมายความว่าเป็นการเหมาะสมกว่าที่จะบอกว่า สภาพแวดล้อมของฟังก์ชั่นแลมบ์ดา "เป็นการปิดตัวลงหรือไม่?
Jeff M

3
แม้ว่าแคลคูลัสแลมบ์ดารู้สึกเหมือนภาษาเครื่องสำหรับฉัน แต่ฉันต้องเห็นพ้องว่ามันเป็น "พบ" ภาษาตรงกันข้ามกับภาษา "ทำ" และน้อยกว่ามากภายใต้อนุสัญญาโดยพลการและมีความเหมาะสมมากกว่าในการรวบรวมโครงสร้างพื้นฐานของความเป็นจริง เราสามารถหาข้อมูลเฉพาะใน Linq, JavaScript, F # เข้าถึงได้ / เข้าถึงได้มากขึ้น แต่แคลคูลัสแลมบ์ดาได้รับจำนวนของเรื่องโดยไม่มีการรบกวน
StevePoling

1
ฉันขอขอบคุณที่คุณย้ำจุดของคุณหลายครั้งด้วยถ้อยคำที่แตกต่างกันเล็กน้อยในแต่ละครั้ง ช่วยเสริมแนวคิด ฉันหวังว่าผู้คนมากขึ้นทำสิ่งนี้
johnklawlor

175

เมื่อคนส่วนใหญ่นึกถึงฟังก์ชั่นพวกเขาคิดถึงฟังก์ชั่นที่ตั้งชื่อ :

function foo() { return "This string is returned from the 'foo' function"; }

แน่นอนว่ามีชื่อเหล่านี้เรียกว่า:

foo(); //returns the string above

ด้วยการแสดงออกแลมบ์ดาคุณสามารถมีฟังก์ชั่นที่ไม่ระบุชื่อ :

 @foo = lambda() {return "This is returned from a function without a name";}

จากตัวอย่างข้างต้นคุณสามารถเรียกแลมบ์ดาผ่านตัวแปรที่กำหนดให้:

foo();

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

function filter(list, predicate) 
 { @filteredList = [];
   for-each (@x in list) if (predicate(x)) filteredList.add(x);
   return filteredList;
 }

//filter for even numbers
filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)}); 

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

@x = 0;

function incrementX() { x = x + 1;}

incrementX(); // x now equals 1

ดูเหมือนจะไม่มากนัก แต่ถ้าเป็นเช่นนั้นในฟังก์ชั่นอื่นและคุณส่งincrementXไปยังฟังก์ชั่นภายนอก

function foo()
 { @x = 0;

   function incrementX() 
    { x = x + 1;
      return x;
    }

   return incrementX;
 }

@y = foo(); // y = closure of incrementX over foo.x
y(); //returns 1 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)

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

function foo()
 { @x = 0;

   return lambda() 
           { x = x + 1;
             return x;
           };
 }

14
คุณอยู่ที่นี่ภาษาอะไร
Claudiu

7
มันเป็นรหัสเทียมโดยทั่วไป มีบางเสียงกระเพื่อมและ JavaScript ในนั้นรวมถึงภาษาที่ฉันออกแบบเรียกว่า "@" ("ที่") ตั้งชื่อตามตัวดำเนินการประกาศตัวแปร
Mark Cidade

3
@MarkCidade ภาษานี้อยู่ที่ไหน @ @ มีเอกสารและ donwload หรือไม่
Pacerier

5
ทำไมไม่ใช้ Javascript และเพิ่มข้อ จำกัด ในการประกาศตัวแปรด้วยเครื่องหมาย @ หน้า ที่จะประหยัดเวลาเล็ก ๆ น้อย ๆ :)
Nemoden

5
@Pacerier: ฉันเริ่มใช้ภาษา: github.com/marxidad/At2015
Mark Cidade

54

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

แลมบ์ดาเป็นฟังก์ชั่นที่กำหนดอินไลน์แทนวิธีมาตรฐานในการประกาศฟังก์ชั่น แลมบ์ดาสามารถส่งผ่านเป็นวัตถุได้บ่อยครั้ง

การปิดเป็นฟังก์ชั่นที่ล้อมรอบสถานะโดยการอ้างอิงเขตข้อมูลภายนอกร่างกาย สถานะที่ถูกปิดล้อมยังคงข้ามการร้องขอการปิด

ในภาษาเชิงวัตถุการปิดโดยปกติจะมีให้ผ่านทางวัตถุ อย่างไรก็ตามบางภาษา OO (เช่น C #) ใช้ฟังก์ชันพิเศษที่ใกล้เคียงกับคำจำกัดความของการปิดที่จัดทำโดยภาษาที่ใช้งานได้จริง (เช่นเสียงกระเพื่อม) ที่ไม่มีวัตถุที่จะล้อมรอบสถานะ

สิ่งที่น่าสนใจคือการนำ Lambdas และ Closures ใน C # มาใช้ทำให้การเขียนโปรแกรมใช้งานได้ใกล้เคียงกับการใช้งานหลัก


ดังนั้นเราสามารถพูดได้ไหมว่าการปิดเป็นชุดย่อยของ lambdas และ lambdas เป็นชุดย่อยของฟังก์ชั่น?
Sker

การปิดเป็นส่วนย่อยของ lambdas ... แต่ lambdas นั้นพิเศษกว่าฟังก์ชั่นทั่วไป อย่างที่ฉันพูดแลมบ์ดามีการกำหนดแบบอินไลน์ โดยพื้นฐานแล้วจะไม่มีวิธีการอ้างอิงพวกเขาจนกว่าพวกเขาจะถูกส่งผ่านไปยังฟังก์ชั่นอื่นหรือกลับเป็นค่าตอบแทน
Michael Brown

19
Lambdas และ closures เป็นส่วนย่อยของทุกฟังก์ชั่น แต่มีเพียงจุดตัดระหว่าง lambdas และ closures โดยที่ส่วนที่ไม่ได้ตัดกันของการปิดจะถูกตั้งชื่อฟังก์ชั่นที่เป็น closures และ lamdas ที่ไม่ตัดกันเป็นฟังก์ชันในตัวเอง ตัวแปรที่ถูกผูกไว้
Mark Cidade

ในความคิดของฉันแลมบ์ดาเป็นแนวคิดพื้นฐานมากกว่าฟังก์ชั่น มันขึ้นอยู่กับภาษาการเขียนโปรแกรมจริงๆ
Wei Qiu

15

มันง่ายอย่างนี้แลมบ์ดาเป็นภาษาที่สร้างขึ้นนั่นคือไวยากรณ์สำหรับฟังก์ชันที่ไม่ระบุชื่อ การปิดเป็นเทคนิคในการใช้งาน - หรือฟังก์ชั่นชั้นหนึ่งใด ๆ สำหรับเรื่องนั้นชื่อหรือไม่ระบุชื่อ

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

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


12

จากมุมมองของภาษาการเขียนโปรแกรมพวกเขาเป็นสองสิ่งที่แตกต่างกันอย่างสมบูรณ์

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

แลมบ์ดานำเสนอวิธีที่คุณสามารถสรุปกระบวนการคำนวณได้ ตัวอย่างเช่นในการคำนวณผลรวมของตัวเลขสองจำนวนกระบวนการที่ใช้สองพารามิเตอร์ x, y และส่งกลับ x + y สามารถแยกออก ในแบบแผนคุณสามารถเขียนมันเป็น

(lambda (x y) (+ x y))

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

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

((lambda (x y) (+ x y)) 2 3)

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

ด้วยพื้นหลังนี้ให้ตรวจสอบฟังก์ชั่นนี้:

(lambda (x y) (+ x y z))

เรารู้ว่าเมื่อเราประเมินการแสดงออกแลมบ์ดา xy จะถูกผูกไว้ในตารางใหม่ แต่เราจะค้นหา z ได้อย่างไรและที่ไหน? จริงๆแล้ว z เรียกว่าตัวแปรอิสระ จะต้องมีสภาพแวดล้อมภายนอกซึ่งมี z มิฉะนั้นความหมายของนิพจน์ไม่สามารถระบุได้โดยการผูก x และ y เท่านั้น เพื่อให้ชัดเจนคุณสามารถเขียนสิ่งต่อไปนี้ในรูปแบบ:

((lambda (z) (lambda (x y) (+ x y z))) 1)

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

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

ฉันใช้รูปแบบเพื่อแสดงความคิดเห็นเนื่องจากรูปแบบนั้นเป็นหนึ่งในภาษาแรกสุดที่มีการปิดจริง วัสดุทั้งหมดที่นี่นำเสนอได้ดีกว่ามากใน SICP บทที่ 3

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


ดังนั้นหนึ่งสามารถทดแทนการปิดทั้งหมดโดย lambdas ที่ซ้อนกันจนกว่าจะไม่มีตัวแปรว่างอยู่อีกต่อไป? ในกรณีนี้ฉันจะบอกว่าการปิดสามารถถูกมองว่าเป็นลูกแกะชนิดพิเศษ
Trilarion

8

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

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

function ($ v) {return $ v> 2; } คือนิยามฟังก์ชั่นแลมบ์ดา เราสามารถเก็บไว้ในตัวแปรได้ดังนั้นจึงสามารถใช้ซ้ำได้:

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

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

$max_comp = function ($max) {
  return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));

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

นี่เป็นตัวอย่างที่ง่ายกว่าของการปิด PHP:

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

อธิบายไว้อย่างดีในบทความนี้


7

คำถามนี้เก่าและได้คำตอบมากมาย
ขณะนี้มี Java 8 และแลมบ์ดาอย่างเป็นทางการซึ่งเป็นโครงการปิดที่ไม่เป็นทางการ

คำตอบในบริบท Java (ผ่านLambdas และการปิด - ความแตกต่างคืออะไร ):

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


การปิดดำเนินการของ Lamdas ใน Java เป็นอย่างไร หมายความว่านิพจน์ Lamdas ถูกแปลงเป็นคลาสแบบไม่ระบุชื่อแบบเก่าหรือไม่?
hackjutsu

5

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


4

การแสดงออกของแลมบ์ดาเป็นเพียงฟังก์ชั่นที่ไม่ระบุชื่อ ใน Java ธรรมดาคุณสามารถเขียนได้เช่นนี้

Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
    public Job apply(Person person) {
        Job job = new Job(person.getPersonId(), person.getJobDescription());
        return job;
    }
};

โดยที่ Function คลาสเพิ่งสร้างขึ้นในรหัส java ตอนนี้คุณสามารถโทรหาที่mapPersonToJob.apply(person)อื่นเพื่อใช้งานได้ นั่นเป็นเพียงตัวอย่างเดียว นั่นเป็นแลมบ์ดาก่อนที่จะมีไวยากรณ์สำหรับมัน แลมบ์ดาทำสิ่งนี้สั้น ๆ

ปิด:

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

ใน Kotlin แลมบ์ดาสามารถเข้าถึงการปิดได้ตลอดเวลา (ตัวแปรที่อยู่ในขอบเขตด้านนอก)


0

มันขึ้นอยู่กับว่าฟังก์ชั่นใช้ตัวแปรภายนอกหรือไม่เพื่อดำเนินการ

ตัวแปรภายนอก - ตัวแปรที่กำหนดไว้นอกขอบเขตของฟังก์ชัน

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

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Closures hold stateเนื่องจากใช้ตัวแปรภายนอก (เช่นตัวแปรที่กำหนดนอกขอบเขตของฟังก์ชัน) พร้อมกับพารามิเตอร์และค่าคงที่เพื่อดำเนินการ

    int n = 2
    
    Function<Integer,Integer> closure = t -> {
        return t * n 
    }
    

เมื่อ Java สร้างการปิดมันจะเก็บตัวแปร n ไว้กับฟังก์ชั่นเพื่อให้สามารถอ้างอิงได้เมื่อถูกส่งไปยังฟังก์ชั่นอื่นหรือใช้ที่ใดก็ได้

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