มีคนอธิบายได้ไหม ฉันเข้าใจแนวคิดพื้นฐานที่อยู่เบื้องหลังพวกเขา แต่ฉันมักจะเห็นพวกเขาใช้สลับกันได้และฉันสับสน
และตอนนี้เราอยู่ที่นี่พวกเขาแตกต่างจากฟังก์ชั่นปกติอย่างไร
มีคนอธิบายได้ไหม ฉันเข้าใจแนวคิดพื้นฐานที่อยู่เบื้องหลังพวกเขา แต่ฉันมักจะเห็นพวกเขาใช้สลับกันได้และฉันสับสน
และตอนนี้เราอยู่ที่นี่พวกเขาแตกต่างจากฟังก์ชั่นปกติอย่างไร
คำตอบ:
แลมบ์ดาเป็นเพียงฟังก์ชั่นที่ไม่ระบุชื่อ - ฟังก์ชั่นที่กำหนดไว้ไม่มีชื่อ ในบางภาษาเช่น 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
สิ่งนี้ตามที่คุณสังเกตเห็นไม่มีอะไรเกี่ยวข้องกับแลมบ์ดา - มันเป็นแนวคิดที่แตกต่างกันสองแบบ (แม้ว่าเกี่ยวข้องกัน)
มีความสับสนรอบ ๆ 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
.
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 กล่าวว่า: "มีปลาที่ใหญ่กว่าเสมอ";)) หรือในห้องสมุดบางแห่งหรือในภาษา ( ดั้งเดิม )
สิ่งนี้ช่วยให้เราแบ่งการแสดงออกแลมบ์ดาออกเป็นสองประเภท:
คุณสามารถปิดการแสดงออกแลมบ์ดาที่เปิดโดยการจัดหาสภาพแวดล้อมซึ่งกำหนดสัญลักษณ์ฟรีเหล่านี้โดยการผูกพวกเขากับค่าบางอย่าง (ซึ่งอาจเป็นตัวเลขสตริงฟังก์ชั่นที่ไม่ระบุชื่อ 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
เอาล่ะตลาดองค์กรของ 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 และช่วยให้พวกเขาเข้าถึงสภาพแวดล้อมที่ล้อมรอบของพวกเขาเช่นเดียวกับรหัสเครื่องเพื่อทำงานในบริบทนั้น แต่มันเป็นเพียงวิธีหนึ่งในหลาย ๆ วิธีในการปิดระบบไม่ใช่การปิดตัวเอง
ดังที่ฉันได้อธิบายไว้ข้างต้นการปิดแลมบ์ดานิพจน์เป็นส่วนย่อยของคำจำกัดความในสภาพแวดล้อมที่ให้ค่ากับตัวแปรอิสระที่มีอยู่ในนิพจน์แลมบ์ดานั้นการปิดนิพจน์อย่างมีประสิทธิภาพ(การเปลี่ยนนิพจน์แลมบ์ดาแบบเปิดแลมบ์ดานิพจน์แบบปิดซึ่งสามารถประเมินได้เนื่องจากสัญลักษณ์ทั้งหมดที่อยู่ในนั้นถูกกำหนด)
สิ่งอื่นใดเป็นเพียง "ลัทธิการขนส่งสินค้า" และ "เวทมนต์วิเศษ" ของโปรแกรมเมอร์และผู้ขายภาษาโดยไม่รู้ตัวถึงรากที่แท้จริงของความคิดเหล่านี้
ฉันหวังว่าจะตอบคำถามของคุณ แต่ถ้าคุณมีคำถามติดตามอย่าลังเลที่จะถามพวกเขาในความคิดเห็นและฉันจะพยายามอธิบายให้ดีขึ้น
เมื่อคนส่วนใหญ่นึกถึงฟังก์ชั่นพวกเขาคิดถึงฟังก์ชั่นที่ตั้งชื่อ :
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;
};
}
ไม่ใช่การปิดทั้งหมดเป็น lambdas และไม่ใช่ lambdas ทั้งหมดที่ปิดอยู่ ทั้งสองเป็นฟังก์ชั่น แต่ไม่จำเป็นต้องอยู่ในลักษณะที่เราคุ้นเคย
แลมบ์ดาเป็นฟังก์ชั่นที่กำหนดอินไลน์แทนวิธีมาตรฐานในการประกาศฟังก์ชั่น แลมบ์ดาสามารถส่งผ่านเป็นวัตถุได้บ่อยครั้ง
การปิดเป็นฟังก์ชั่นที่ล้อมรอบสถานะโดยการอ้างอิงเขตข้อมูลภายนอกร่างกาย สถานะที่ถูกปิดล้อมยังคงข้ามการร้องขอการปิด
ในภาษาเชิงวัตถุการปิดโดยปกติจะมีให้ผ่านทางวัตถุ อย่างไรก็ตามบางภาษา OO (เช่น C #) ใช้ฟังก์ชันพิเศษที่ใกล้เคียงกับคำจำกัดความของการปิดที่จัดทำโดยภาษาที่ใช้งานได้จริง (เช่นเสียงกระเพื่อม) ที่ไม่มีวัตถุที่จะล้อมรอบสถานะ
สิ่งที่น่าสนใจคือการนำ Lambdas และ Closures ใน C # มาใช้ทำให้การเขียนโปรแกรมใช้งานได้ใกล้เคียงกับการใช้งานหลัก
มันง่ายอย่างนี้แลมบ์ดาเป็นภาษาที่สร้างขึ้นนั่นคือไวยากรณ์สำหรับฟังก์ชันที่ไม่ระบุชื่อ การปิดเป็นเทคนิคในการใช้งาน - หรือฟังก์ชั่นชั้นหนึ่งใด ๆ สำหรับเรื่องนั้นชื่อหรือไม่ระบุชื่อ
แม่นยำยิ่งขึ้นการปิดเป็นวิธีการแสดงฟังก์ชั่นชั้นหนึ่งที่รันไทม์ในขณะที่คู่ของ "รหัส" และสภาพแวดล้อม "ปิด" กับตัวแปรที่ไม่ใช่ท้องถิ่นทั้งหมดที่ใช้ในรหัสที่ ด้วยวิธีนี้ตัวแปรเหล่านั้นยังคงสามารถเข้าถึงได้แม้ในขณะที่ขอบเขตด้านนอกที่กำเนิดได้ถูกออกไปแล้ว
น่าเสียดายที่มีหลายภาษาที่ไม่รองรับฟังก์ชั่นเป็นค่าที่ดีเลิศหรือสนับสนุนเฉพาะในรูปแบบที่พิการ ดังนั้นผู้คนมักจะใช้คำว่า "การปิด" เพื่อแยกความแตกต่าง "ของจริง"
จากมุมมองของภาษาการเขียนโปรแกรมพวกเขาเป็นสองสิ่งที่แตกต่างกันอย่างสมบูรณ์
โดยทั่วไปสำหรับภาษาทัวริงที่สมบูรณ์เราต้องการเพียงองค์ประกอบที่ จำกัด มากเช่นนามธรรมการประยุกต์ใช้และการลด สิ่งที่เป็นนามธรรมและแอพพลิเคชั่นเป็นวิธีที่คุณสามารถสร้างการแสดงออกของ 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
โดยสรุปแลมบ์ดาและการปิดเป็นแนวคิดที่แตกต่างกันจริงๆ แลมบ์ดาเป็นฟังก์ชั่น การปิดคือคู่ของแลมบ์ดาและสภาพแวดล้อมที่สอดคล้องกันซึ่งปิดแลมบ์ดา
แนวคิดเหมือนกับที่อธิบายข้างต้น แต่ถ้าคุณมาจากพื้นหลังของ 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();
คำถามนี้เก่าและได้คำตอบมากมาย
ขณะนี้มี Java 8 และแลมบ์ดาอย่างเป็นทางการซึ่งเป็นโครงการปิดที่ไม่เป็นทางการ
คำตอบในบริบท Java (ผ่านLambdas และการปิด - ความแตกต่างคืออะไร ):
"การปิดคือนิพจน์แลมบ์ดาที่จับคู่กับสภาพแวดล้อมที่ผูกตัวแปรอิสระแต่ละตัวกับค่าใน Java การแสดงออกแลมบ์ดาจะถูกนำไปใช้โดยการปิดดังนั้นทั้งสองคำจึงถูกนำมาใช้แทนกันในชุมชน"
การพูดปิดเป็นกลอุบายเกี่ยวกับขอบเขตแลมบ์ดาเป็นฟังก์ชั่นนิรนาม เราสามารถตระหนักถึงการปิดด้วยแลมบ์ดาได้อย่างสวยงามยิ่งขึ้นและแลมบ์ดามักถูกใช้เป็นพารามิเตอร์ที่ส่งผ่านไปยังฟังก์ชันที่สูงขึ้น
การแสดงออกของแลมบ์ดาเป็นเพียงฟังก์ชั่นที่ไม่ระบุชื่อ ใน 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 แลมบ์ดาสามารถเข้าถึงการปิดได้ตลอดเวลา (ตัวแปรที่อยู่ในขอบเขตด้านนอก)
มันขึ้นอยู่กับว่าฟังก์ชั่นใช้ตัวแปรภายนอกหรือไม่เพื่อดำเนินการ
ตัวแปรภายนอก - ตัวแปรที่กำหนดไว้นอกขอบเขตของฟังก์ชัน
นิพจน์แลมบ์ดานั้นไร้สัญชาติเพราะขึ้นอยู่กับพารามิเตอร์ตัวแปรภายในหรือค่าคงที่เพื่อดำเนินการ
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 ไว้กับฟังก์ชั่นเพื่อให้สามารถอ้างอิงได้เมื่อถูกส่งไปยังฟังก์ชั่นอื่นหรือใช้ที่ใดก็ได้