Sự khác biệt giữa 'đóng cửa' và 'lambda' là gì?


811

Ai đó có thể giải thích? Tôi hiểu các khái niệm cơ bản đằng sau chúng nhưng tôi thường thấy chúng được sử dụng thay thế cho nhau và tôi bị lẫn lộn.

Và bây giờ chúng ta ở đây, chúng khác với chức năng thông thường như thế nào?


85
Lambdas là một cấu trúc ngôn ngữ (hàm ẩn danh), bao đóng là một kỹ thuật triển khai để thực hiện các hàm hạng nhất (cho dù ẩn danh hay không). Thật không may, điều này thường bị nhầm lẫn bởi nhiều người.
Andreas Rossberg



Đối với các bao đóng PHP, hãy xem php.net/manual/en/ class.clenses.php . Nó không phải là những gì một lập trình viên JavaScript mong đợi.
PaulH

Câu trả lời của SasQ là tuyệt vời. IMHO câu hỏi này sẽ hữu ích hơn cho người dùng SO nếu nó hướng dẫn người xem câu trả lời đó.
AmigoNico

Câu trả lời:


703

Một lambda chỉ là một chức năng ẩn danh - một chức năng được xác định không có tên. Trong một số ngôn ngữ, như Scheme, chúng tương đương với các hàm được đặt tên. Trong thực tế, định nghĩa hàm được viết lại dưới dạng ràng buộc lambda với một biến bên trong. Trong các ngôn ngữ khác, như Python, có một số khác biệt (khá bất cần) giữa chúng, nhưng chúng hành xử theo cùng một cách khác.

Một đóng cửa bất kỳ chức năng mà đóng trên các môi trường trong đó nó được xác định. Điều này có nghĩa là nó có thể truy cập các biến không có trong danh sách tham số của nó. Ví dụ:

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

Điều này sẽ gây ra lỗi, vì funckhông đóng trên môi trường trong anotherfunc- hkhông xác định. funcchỉ đóng cửa trên môi trường toàn cầu. Điều này sẽ làm việc:

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

Bởi vì ở đây, funcđược định nghĩa trong anotherfuncvà trong python 2.3 trở lên (hoặc một số như thế này) khi chúng gần như đóng đúng (đột biến vẫn không hoạt động), điều này có nghĩa là nó đóng trên anotherfunc môi trường và có thể truy cập các biến bên trong nó Trong Python 3.1+, đột biến làm việc quá khi sử dụng các nonlocaltừ khóa .

Một điểm quan trọng khác - funcsẽ tiếp tục đóng cửa trên anotherfuncmôi trường ngay cả khi nó không còn được đánh giá anotherfunc. Mã này cũng sẽ hoạt động:

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

print anotherfunc(10)()

Điều này sẽ in 10.

Điều này, như bạn chú ý, không liên quan gì đến lambda - chúng là hai khái niệm khác nhau (mặc dù có liên quan).


1
Claudiu, với kiến ​​thức không chắc chắn của tôi, con trăn chưa bao giờ hoàn toàn đóng cửa đúng. Họ đã khắc phục vấn đề biến đổi trong khi tôi không tìm kiếm? Hoàn toàn có thể ...
simon

simon - bạn nói đúng, họ vẫn chưa hoàn toàn chính xác. Tôi nghĩ python 3.0 sẽ thêm một bản hack để khắc phục điều này. (một cái gì đó giống như một định danh 'không tiêu điểm'). tôi sẽ thay đổi ansewr của tôi để phản ánh điều đó.
Claudiu

2
@AlexanderOrlov: Cả hai đều là lambdas và đóng cửa. Java đã đóng cửa trước đó thông qua các lớp bên trong ẩn danh. Bây giờ chức năng đã được thực hiện dễ dàng hơn về mặt cú pháp thông qua các biểu thức lambda. Vì vậy, có lẽ khía cạnh liên quan nhất của tính năng mới là bây giờ có lambdas. Không phải là không chính xác khi gọi chúng là lambdas, chúng thực sự là lambdas. Tại sao các tác giả Java 8 có thể chọn không làm nổi bật thực tế rằng chúng là các bao đóng không phải là điều tôi biết.
Claudiu

2
@AlexanderOrlov vì lambdas Java 8 không phải là bao đóng thực sự, chúng là mô phỏng của các bao đóng. Chúng tương tự như các bao đóng Python 2.3 (không có tính biến đổi, do đó, yêu cầu đối với các biến được tham chiếu là 'cuối cùng có hiệu quả') và biên dịch nội bộ thành các hàm không đóng, lấy tất cả các biến được tham chiếu trong phạm vi kèm theo làm tham số ẩn.
Pickup Logan

7
@Claudiu Tôi nghĩ rằng tham chiếu đến việc thực hiện ngôn ngữ cụ thể (Python) có thể làm phức tạp quá mức câu trả lời. Câu hỏi hoàn toàn không biết ngôn ngữ (cũng như không có thẻ dành riêng cho ngôn ngữ).
Matthew

318

Có rất nhiều nhầm lẫn xung quanh lambdas và đóng cửa, ngay cả trong các câu trả lời cho câu hỏi StackOverflow này ở đây. Thay vì hỏi các lập trình viên ngẫu nhiên, những người đã học về việc đóng cửa từ thực tiễn với các ngôn ngữ lập trình nhất định hoặc các lập trình viên không biết gì khác, hãy thực hiện một hành trình đến nguồn (nơi tất cả bắt đầu). Và vì lambdas và đóng cửa đến từ Lambda Compus được phát minh bởi Alonzo Church vào những năm 30 trước khi máy tính điện tử đầu tiên tồn tại, đây là nguồn mà tôi đang nói đến.

Lambda Tính là ngôn ngữ lập trình đơn giản nhất trên thế giới. Những điều duy nhất bạn có thể làm trong đó: ►

  • ỨNG DỤNG: Áp dụng một biểu thức cho một biểu thức khác, ký hiệu f x.
    (Hãy nghĩ về nó như một lời gọi hàm, hàm ở đâu fxlà tham số duy nhất của nó)
  • TÓM TẮT: Liên kết một biểu tượng xuất hiện trong một biểu thức để đánh dấu rằng biểu tượng này chỉ là một "khe", một ô trống đang chờ để được điền với giá trị, là "biến". Nó được thực hiện bằng cách thêm một chữ cái Hy Lạp λ(lambda), sau đó là tên tượng trưng (ví dụ x), sau đó là một dấu chấm .trước biểu thức. Điều này sau đó chuyển đổi biểu thức thành một hàm mong đợi một tham số .
    Ví dụ: λx.x+2lấy biểu thức x+2và nói rằng biểu tượng xtrong biểu thức này là một biến bị ràng buộc - nó có thể được thay thế bằng một giá trị bạn cung cấp làm tham số.
    Lưu ý rằng hàm được định nghĩa theo cách này là ẩn danh- nó không có tên, vì vậy bạn chưa thể gọi nó, nhưng bạn có thể gọi ngay (nhớ ứng dụng?) Bằng cách cung cấp cho nó tham số mà nó đang chờ, như thế này : (λx.x+2) 7. Sau đó, biểu thức (trong trường hợp này là một giá trị theo nghĩa đen) 7được thay thế như xtrong phần phụ x+2của lambda được áp dụng, do đó bạn nhận được 7+2, sau đó giảm xuống 9theo quy tắc arithologists thông thường.

Vì vậy, chúng tôi đã giải quyết một trong những bí ẩn:
lambdahàm ẩn danh từ ví dụ trên , λx.x+2.


Trong các ngôn ngữ lập trình khác nhau, cú pháp trừu tượng hóa chức năng (lambda) có thể khác nhau. Ví dụ: trong JavaScript, nó trông như thế này:

function(x) { return x+2; }

và bạn có thể ngay lập tức áp dụng nó cho một số tham số như thế này:

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

hoặc bạn có thể lưu trữ hàm ẩn danh này (lambda) vào một số biến:

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

cái tên này có hiệu quả f, cho phép bạn tham khảo và gọi nó nhiều lần sau đó, vd:

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

Nhưng bạn đã không phải đặt tên cho nó. Bạn có thể gọi nó ngay lập tức:

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

Trong LISP, lambdas được làm như thế này:

(lambda (x) (+ x 2))

và bạn có thể gọi lambda như vậy bằng cách áp dụng nó ngay lập tức vào một tham số:

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


OK, bây giờ là lúc để giải quyết bí ẩn khác: đóng cửa là gì . Để làm điều đó, hãy nói về các ký hiệu ( biến ) trong biểu thức lambda.

Như tôi đã nói, những gì trừu tượng lambda làm là ràng buộc một biểu tượng trong biểu hiện phụ của nó, để nó trở thành một tham số thay thế . Một biểu tượng như vậy được gọi là ràng buộc . Nhưng nếu có các biểu tượng khác trong biểu thức thì sao? Ví dụ : λx.x/y+2. Trong biểu thức này, biểu tượng xbị ràng buộc bởi sự trừu tượng lambda λx.trước nó. Nhưng biểu tượng khác, ykhông bị ràng buộc - nó miễn phí . Chúng tôi không biết nó là gì và nó đến từ đâu, vì vậy chúng tôi không biết ý nghĩa của nó và giá trị của gì , và do đó chúng tôi không thể đánh giá biểu thức đó cho đến khi chúng tôi hiểu được ý ynghĩa của nó.

Trong thực tế, cùng đi với hai biểu tượng khác, 2+. Chỉ là chúng ta đã quá quen thuộc với hai biểu tượng này đến nỗi chúng ta thường quên rằng máy tính không biết chúng và chúng ta cần nói cho chúng biết ý nghĩa của chúng bằng cách xác định chúng ở đâu đó, ví dụ như trong thư viện hoặc chính ngôn ngữ.

Bạn có thể nghĩ về các biểu tượng miễn phí như được định nghĩa ở một nơi khác, bên ngoài biểu thức, trong "bối cảnh xung quanh", được gọi là môi trường của nó . Môi trường có thể là một biểu hiện lớn hơn mà biểu thức này là một phần của (như Qui-Gon Jinn nói: "Luôn có một con cá lớn hơn";)), hoặc trong một thư viện, hoặc trong chính ngôn ngữ (như một ngôn ngữ nguyên thủy ).

Điều này cho phép chúng tôi chia biểu thức lambda thành hai loại:

  • Biểu thức ĐÓNG: mọi biểu tượng xuất hiện trong các biểu thức này bị ràng buộc bởi một số trừu tượng lambda. Nói cách khác, chúng là khép kín ; họ không yêu cầu bất kỳ bối cảnh xung quanh để được đánh giá. Họ cũng được gọi là tổ hợp .
  • Biểu thức MỞ: một số biểu tượng trong các biểu thức này không bị ràng buộc - nghĩa là, một số biểu tượng xuất hiện trong chúng là miễn phí và chúng yêu cầu một số thông tin bên ngoài, do đó chúng không thể được đánh giá cho đến khi bạn cung cấp định nghĩa của các biểu tượng này.

Bạn có thể ĐÓNG một biểu thức lambda mở bằng cách cung cấp môi trường , định nghĩa tất cả các biểu tượng miễn phí này bằng cách ràng buộc chúng với một số giá trị (có thể là số, chuỗi, hàm ẩn danh hay còn gọi là lambdas, bất cứ điều gì).

Và ở đây có sự đóng cửa một phần:
Các đóng cửa của một biểu thức lambda là tập hợp cụ thể này các biểu tượng được định nghĩa trong bối cảnh bên ngoài (môi trường) mà cung cấp cho giá trị vào những biểu tượng miễn phí trong biểu thức này, làm cho chúng không tự do nữa. Nó biến một biểu thức lambda mở , vẫn chứa một số biểu tượng miễn phí "không xác định", thành biểu tượng đóng , không còn biểu tượng miễn phí nào nữa.

Ví dụ, nếu bạn có các biểu thức lambda sau: λx.x/y+2, biểu tượng xlà ràng buộc, trong khi biểu tượng ylà miễn phí, do đó biểu thức là openvà không thể được đánh giá, trừ khi bạn nói gì ynghĩa là (và cùng với +2, cũng là miễn phí). Nhưng giả sử rằng bạn cũng có một môi trường như thế này:

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

Đây môi trường cung cấp các định nghĩa cho tất cả các "undefined" (miễn phí) biểu tượng từ biểu thức lambda của chúng tôi ( y, +, 2), và một số những biểu tượng thêm ( q, w). Các biểu tượng mà chúng ta cần được xác định là tập hợp con của môi trường này:

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

và đây chính xác là sự đóng cửa của biểu thức lambda của chúng tôi:>

Nói cách khác, nó đóng một biểu thức lambda mở. Đây là nơi đóng cửa tên đến từ nơi đầu tiên, và đây là lý do tại sao rất nhiều câu trả lời của mọi người trong chủ đề này không hoàn toàn chính xác: P


Vậy tại sao họ nhầm? Tại sao nhiều người trong số họ nói rằng việc đóng cửa là một số cấu trúc dữ liệu trong bộ nhớ hoặc một số tính năng của ngôn ngữ họ sử dụng hoặc tại sao họ nhầm lẫn giữa việc đóng cửa với lambdas? : P

Chà, các thị trường doanh nghiệp của Sun / Oracle, Microsoft, Google, v.v ... đều đáng trách, bởi vì đó là cái mà họ gọi là các cấu trúc này trong ngôn ngữ của họ (Java, C #, Go, v.v.). Họ thường gọi "đóng cửa" những gì được cho là chỉ là lambdas. Hoặc họ gọi "đóng cửa" là một kỹ thuật cụ thể mà họ đã sử dụng để thực hiện phạm vi từ vựng, nghĩa là thực tế là một hàm có thể truy cập các biến được xác định trong phạm vi bên ngoài tại thời điểm định nghĩa của nó. Họ thường nói rằng hàm "bao quanh" các biến này, nghĩa là, bắt chúng vào một số cấu trúc dữ liệu để cứu chúng khỏi bị phá hủy sau khi hàm ngoài hoàn thành việc thực thi. Nhưng đây chỉ là một bài viết "tiếp cận văn hóa dân gian" và tiếp thị, mà chỉ khiến mọi thứ trở nên khó hiểu hơn,

Và nó thậm chí còn tồi tệ hơn vì thực tế là luôn có một chút sự thật trong những gì họ nói, điều đó không cho phép bạn dễ dàng bác bỏ nó là sai: P Hãy để tôi giải thích:

Nếu bạn muốn triển khai một ngôn ngữ sử dụng lambdas như công dân hạng nhất, bạn cần cho phép họ sử dụng các biểu tượng được xác định trong ngữ cảnh xung quanh (nghĩa là sử dụng các biến miễn phí trong lambdas của bạn). Và các ký hiệu này phải ở đó ngay cả khi hàm xung quanh trở lại. Vấn đề là các ký hiệu này bị ràng buộc với một số lưu trữ cục bộ của hàm (thường là trên ngăn xếp cuộc gọi), sẽ không còn ở đó nữa khi hàm trả về. Do đó, để lambda hoạt động theo cách bạn mong đợi, bạn cần bằng cách nào đó "nắm bắt" tất cả các biến miễn phí này từ bối cảnh bên ngoài của nó và lưu chúng cho sau này, ngay cả khi bối cảnh bên ngoài sẽ biến mất. Đó là, bạn cần phải tìm sự đóng cửacủa lambda của bạn (tất cả các biến bên ngoài mà nó sử dụng) và lưu trữ nó ở một nơi khác (bằng cách tạo một bản sao hoặc bằng cách chuẩn bị không gian cho chúng trả trước, ở một nơi khác ngoài ngăn xếp). Phương pháp thực tế bạn sử dụng để đạt được mục tiêu này là "chi tiết triển khai" ngôn ngữ của bạn. Điều quan trọng ở đây là việc đóng cửa , đó là tập hợp các biến miễn phí từ môi trường lambda của bạn cần được lưu ở đâu đó.

Mọi người đã không mất quá nhiều thời gian để bắt đầu gọi cấu trúc dữ liệu thực tế mà họ sử dụng trong các triển khai ngôn ngữ của họ để thực hiện việc đóng cửa như là "đóng cửa". Cấu trúc thường trông giống như thế này:

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

và các cấu trúc dữ liệu này đang được truyền xung quanh dưới dạng tham số cho các hàm khác, được trả về từ các hàm và được lưu trữ trong các biến, để biểu diễn lambdas và cho phép chúng truy cập vào môi trường kèm theo cũng như mã máy để chạy trong ngữ cảnh đó. Nhưng đó chỉ là một cách (một trong nhiều) để thực hiện đóng cửa, chứ không phải đóng cửa.

Như tôi đã giải thích ở trên, việc đóng biểu thức lambda là tập hợp con của các định nghĩa trong môi trường của nó cung cấp các giá trị cho các biến miễn phí có trong biểu thức lambda đó, đóng biểu thức một cách hiệu quả (biến một biểu thức lambda mở , chưa thể đánh giá được một biểu thức lambda đóng , sau đó có thể được đánh giá, vì tất cả các biểu tượng có trong nó hiện được xác định).

Bất cứ điều gì khác chỉ là một "giáo phái vận chuyển hàng hóa" và "ma thuật voo-doo" của các lập trình viên và nhà cung cấp ngôn ngữ không biết về nguồn gốc thực sự của những khái niệm này.

Tôi hy vọng đó là câu trả lời của bạn. Nhưng nếu bạn có bất kỳ câu hỏi tiếp theo nào, vui lòng hỏi họ trong các nhận xét và tôi sẽ cố gắng giải thích rõ hơn.


50
Câu trả lời hay nhất giải thích mọi thứ một cách khái quát hơn là ngôn ngữ cụ thể
Shishir Arora

39
Tôi thích cách tiếp cận này khi giải thích mọi thứ. Bắt đầu từ đầu, giải thích cách mọi thứ hoạt động và sau đó làm thế nào những quan niệm sai lầm hiện tại được tạo ra. Câu trả lời này cần phải đi đến đầu trang.
Sharky

1
@SasQ> việc đóng biểu thức lambda là tập hợp con của các định nghĩa trong môi trường của nó cung cấp các giá trị cho các biến miễn phí có trong biểu thức lambda đó <Vì vậy, trong cấu trúc dữ liệu mẫu của bạn, điều đó có nghĩa phù hợp hơn khi nói "con trỏ tới môi trường chức năng lambda "là đóng cửa?
Jeff M

3
Mặc dù tính toán Lambda cảm thấy giống như ngôn ngữ máy đối với tôi nhưng tôi phải đồng ý rằng đó là ngôn ngữ "tìm thấy" trái ngược với ngôn ngữ "được tạo ra". Và do đó ít chịu sự điều chỉnh của các quy ước tùy ý và phù hợp hơn nhiều với việc nắm bắt cấu trúc cơ bản của thực tế. Chúng ta có thể tìm thấy các chi tiết cụ thể trong Linq, JavaScript, F # dễ tiếp cận / dễ tiếp cận hơn, nhưng tính toán Lambda có thể đi sâu vào vấn đề mà không bị phân tâm.
StevePoling

1
Tôi đánh giá cao rằng bạn đã nhắc lại quan điểm của bạn nhiều lần, với cách diễn đạt hơi khác nhau mỗi lần. Nó giúp củng cố khái niệm. Tôi muốn nhiều người làm điều này.
johnklawlor

175

Khi hầu hết mọi người nghĩ về các chức năng , họ nghĩ về các chức năng được đặt tên :

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

Chúng được gọi theo tên, tất nhiên:

foo(); //returns the string above

Với biểu thức lambda , bạn có thể có các hàm ẩn danh :

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

Với ví dụ trên, bạn có thể gọi lambda thông qua biến được gán cho:

foo();

Tuy nhiên, hữu ích hơn việc gán các hàm ẩn danh cho các biến, tuy nhiên, việc chuyển chúng đến hoặc từ các hàm bậc cao hơn, tức là các hàm chấp nhận / trả về các hàm khác. Trong rất nhiều trường hợp này, việc đặt tên hàm là không cần thiết:

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)}); 

Một bao đóng có thể là một hàm có tên hoặc ẩn danh, nhưng được biết đến như vậy khi nó "đóng" các biến trong phạm vi mà hàm được định nghĩa, nghĩa là bao đóng sẽ vẫn tham chiếu đến môi trường với bất kỳ biến ngoài nào được sử dụng trong tự đóng cửa. Đây là một đóng cửa có tên:

@x = 0;

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

incrementX(); // x now equals 1

Điều đó dường như không nhiều nhưng nếu điều này là tất cả trong một chức năng khác và bạn đã chuyển incrementXsang một chức năng bên ngoài thì sao?

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)

Đây là cách bạn có được các đối tượng trạng thái trong lập trình chức năng. Vì không cần đặt tên "gia tăngX", bạn có thể sử dụng lambda trong trường hợp này:

function foo()
 { @x = 0;

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

14
bạn đang nói ngôn ngữ gì ở đây
Claudiu

7
Về cơ bản nó là mã giả. Có một số lisp và JavaScript trong đó, cũng như một ngôn ngữ tôi đang thiết kế có tên là "@" ("at"), được đặt tên theo toán tử khai báo biến.
Đánh dấu Cidade

3
@MarkCidade, vậy ngôn ngữ này ở đâu @? Có một tài liệu và donwload?
Pacerier

5
Tại sao không dùng Javascript và thêm một ràng buộc khai báo các biến có dấu @ hàng đầu? Điều đó sẽ tiết kiệm thời gian một chút :)
Nemoden

5
@Pacerier: Tôi bắt đầu triển khai ngôn ngữ: github.com/marxidad/At2015
Mark Cidade

54

Không phải tất cả các bao đóng là lambdas và không phải tất cả các lambdas là đóng cửa. Cả hai đều là chức năng, nhưng không nhất thiết theo cách chúng ta quen biết.

Lambda về cơ bản là một hàm được định nghĩa nội tuyến hơn là phương thức khai báo hàm chuẩn. Lambdas có thể thường xuyên được truyền xung quanh như các đối tượng.

Đóng cửa là một chức năng bao quanh trạng thái xung quanh của nó bằng cách tham chiếu các trường bên ngoài cơ thể của nó. Các trạng thái kèm theo vẫn còn trên các yêu cầu đóng cửa.

Trong một ngôn ngữ hướng đối tượng, các bao đóng thường được cung cấp thông qua các đối tượng. Tuy nhiên, một số ngôn ngữ OO (ví dụ C #) triển khai chức năng đặc biệt gần với định nghĩa về các bao đóng được cung cấp bởi các ngôn ngữ chức năng thuần túy (chẳng hạn như lisp) không có đối tượng để bao quanh trạng thái.

Điều thú vị là việc giới thiệu Lambdas và Closures trong C # mang chương trình chức năng đến gần hơn với cách sử dụng chính.


Vì vậy, chúng ta có thể nói rằng đóng cửa là một tập hợp con của lambdas và lambdas là một tập hợp con của các chức năng?
trượt ván

Đóng cửa là một tập hợp con của lambdas ... nhưng lambdas đặc biệt hơn các chức năng bình thường. Như tôi đã nói, lambdas được định nghĩa nội tuyến. Về cơ bản, không có cách nào để tham chiếu chúng trừ khi chúng được chuyển sang hàm khác hoặc được trả về làm giá trị trả về.
Michael Brown

19
Lambdas và bao đóng là mỗi tập hợp con của tất cả các hàm, nhưng chỉ có một giao điểm giữa lambdas và bao đóng, trong đó phần không giao nhau của các bao đóng sẽ được đặt tên là các hàm đóng và lam không giao nhau là các hàm khép kín có đầy đủ các biến ràng buộc.
Đánh dấu Cidade

Theo tôi, lambdas là khái niệm cơ bản hơn chức năng. Nó thực sự phụ thuộc vào ngôn ngữ lập trình.
Wei Qiu

15

Nó đơn giản như thế này: lambda là một cấu trúc ngôn ngữ, tức là cú pháp đơn giản cho các hàm ẩn danh; đóng cửa là một kỹ thuật để thực hiện nó - hoặc bất kỳ chức năng hạng nhất nào, đối với vấn đề đó, được đặt tên hoặc ẩn danh.

Chính xác hơn, bao đóng là cách một hàm hạng nhất được biểu diễn trong thời gian chạy, như một cặp "mã" của nó và một môi trường "đóng" trên tất cả các biến không cục bộ được sử dụng trong mã đó. Bằng cách này, các biến đó vẫn có thể truy cập được ngay cả khi phạm vi bên ngoài nơi chúng bắt nguồn đã được thoát.

Thật không may, có nhiều ngôn ngữ ngoài kia không hỗ trợ các hàm như các giá trị hạng nhất hoặc chỉ hỗ trợ chúng ở dạng bị tê liệt. Vì vậy, mọi người thường sử dụng thuật ngữ "đóng cửa" để phân biệt "hàng thật".


12

Từ quan điểm của ngôn ngữ lập trình, chúng hoàn toàn là hai thứ khác nhau.

Về cơ bản đối với một ngôn ngữ hoàn chỉnh Turing, chúng ta chỉ cần các yếu tố rất hạn chế, ví dụ trừu tượng hóa, ứng dụng và rút gọn. Tính trừu tượng và ứng dụng cung cấp cách bạn có thể xây dựng biểu thức lamdba và rút gọn làm giảm ý nghĩa của biểu thức lambda.

Lambda cung cấp một cách bạn có thể trừu tượng hóa quá trình tính toán. ví dụ, để tính tổng của hai số, một quá trình lấy hai tham số x, y và trả về x + y có thể được trừu tượng hóa. Trong lược đồ, bạn có thể viết nó dưới dạng

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

Bạn có thể đổi tên các tham số, nhưng tác vụ hoàn thành không thay đổi. Trong hầu hết tất cả các ngôn ngữ lập trình, bạn có thể đặt tên cho biểu thức lambda là các hàm được đặt tên. Nhưng không có nhiều khác biệt, chúng có thể được coi là khái niệm chỉ là cú pháp đường.

OK, bây giờ hãy tưởng tượng làm thế nào điều này có thể được thực hiện. Bất cứ khi nào chúng ta áp dụng biểu thức lambda cho một số biểu thức, vd

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

Chúng ta chỉ có thể thay thế các tham số bằng biểu thức được ước tính. Mô hình này đã rất mạnh mẽ. Nhưng mô hình này không cho phép chúng tôi thay đổi giá trị của các biểu tượng, ví dụ: Chúng tôi không thể bắt chước thay đổi trạng thái. Do đó chúng ta cần một mô hình phức tạp hơn. Để rút gọn, bất cứ khi nào chúng ta muốn tính toán ý nghĩa của biểu thức lambda, chúng ta đặt cặp ký hiệu và giá trị tương ứng vào một môi trường (hoặc bảng). Sau đó, phần còn lại (+ xy) được đánh giá bằng cách tra cứu các ký hiệu tương ứng trong bảng. Bây giờ nếu chúng tôi cung cấp một số nguyên thủy để hoạt động trực tiếp trên môi trường, chúng tôi có thể mô hình hóa các thay đổi của trạng thái!

Với nền tảng này, hãy kiểm tra chức năng này:

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

Chúng tôi biết rằng khi chúng tôi đánh giá biểu thức lambda, xy sẽ bị ràng buộc trong một bảng mới. Nhưng làm thế nào và ở đâu chúng ta có thể nhìn lên z? Trên thực tế z được gọi là một biến miễn phí. Phải có một môi trường bên ngoài có chứa z. Mặt khác, ý nghĩa của biểu thức không thể được xác định chỉ bằng ràng buộc x và y. Để làm rõ điều này, bạn có thể viết một cái gì đó như sau trong sơ đồ:

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

Vì vậy, z sẽ được liên kết với 1 trong một bảng bên ngoài. Chúng ta vẫn nhận được một hàm chấp nhận hai tham số nhưng ý nghĩa thực sự của nó cũng phụ thuộc vào môi trường bên ngoài. Nói cách khác, môi trường bên ngoài đóng trên các biến miễn phí. Với sự trợ giúp của tập hợp!, Chúng ta có thể làm cho hàm trở nên trạng thái, nghĩa là nó không phải là một hàm theo nghĩa toán học. Những gì nó trả về không chỉ phụ thuộc vào đầu vào, mà cả z.

Đây là một cái gì đó bạn đã biết rất rõ, một phương pháp của các đối tượng hầu như luôn dựa vào trạng thái của các đối tượng. Đó là lý do tại sao một số người nói "đóng cửa là đối tượng của người nghèo." Nhưng chúng ta cũng có thể coi các đối tượng là sự đóng cửa của người nghèo vì chúng ta thực sự thích các chức năng hạng nhất.

Tôi sử dụng lược đồ để minh họa các ý tưởng do sơ đồ đó là một trong những ngôn ngữ sớm nhất có sự đóng cửa thực sự. Tất cả các tài liệu ở đây được trình bày tốt hơn nhiều trong SICP chương 3.

Tóm lại, lambda và đóng cửa là những khái niệm thực sự khác nhau. Một lambda là một chức năng. Một bao đóng là một cặp lambda và môi trường tương ứng để đóng lambda.


Vì vậy, người ta có thể thay thế tất cả các lần đóng cửa bằng lambdas lồng nhau cho đến khi không còn biến miễn phí nữa? Trong trường hợp này tôi sẽ nói rằng đóng cửa có thể được coi là loại lambdas đặc biệt.
Trilarion

8

Khái niệm giống như được mô tả ở trên, nhưng nếu bạn đến từ nền tảng PHP, điều này sẽ giải thích thêm bằng cách sử dụng mã PHP.

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

hàm ($ v) {trả lại $ v> 2; } là định nghĩa hàm lambda. Chúng tôi thậm chí có thể lưu trữ nó trong một biến, vì vậy nó có thể được tái sử dụng:

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

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

Bây giờ, nếu bạn muốn thay đổi số lượng tối đa được phép trong mảng được lọc thì sao? Bạn sẽ phải viết một hàm lambda khác hoặc tạo một bao đóng (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));

Một bao đóng là một hàm được đánh giá trong môi trường của chính nó, có một hoặc nhiều biến ràng buộc có thể được truy cập khi hàm được gọi. Họ đến từ thế giới lập trình chức năng, nơi có một số khái niệm đang chơi. Các bao đóng giống như các hàm lambda, nhưng thông minh hơn theo nghĩa là chúng có khả năng tương tác với các biến từ môi trường bên ngoài nơi xác định bao đóng.

Đây là một ví dụ đơn giản hơn về việc đóng PHP:

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

$closure();

Giải thích độc đáo trong bài viết này.


7

Câu hỏi này đã cũ và có nhiều câu trả lời.
Bây giờ với Java 8 và Lambda chính thức là các dự án đóng cửa không chính thức, nó làm sống lại câu hỏi.

Câu trả lời trong ngữ cảnh Java (thông qua Lambdas và bao đóng - có gì khác biệt? ):

"Một bao đóng là một biểu thức lambda được ghép nối với một môi trường liên kết từng biến tự do của nó với một giá trị. Trong Java, các biểu thức lambda sẽ được thực hiện bằng các phương thức đóng, vì vậy hai thuật ngữ đã được sử dụng thay thế cho nhau trong cộng đồng."


Lamdas được triển khai bằng cách đóng trong Java như thế nào? Có nghĩa là biểu thức Lamdas được chuyển đổi thành một lớp ẩn danh kiểu cũ?
hackjutsu

5

Nói một cách đơn giản, đóng cửa là một mẹo về phạm vi, lambda là một chức năng ẩn danh. Chúng ta có thể nhận ra việc đóng với lambda thanh lịch hơn và lambda thường được sử dụng như một tham số được truyền cho hàm cao hơn


4

Một biểu thức Lambda chỉ là một hàm ẩn danh. trong java đơn giản, ví dụ, bạn có thể viết nó như thế này:

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

trong đó lớp Hàm chỉ được xây dựng trong mã java. Bây giờ bạn có thể gọi mapPersonToJob.apply(person)một nơi nào đó để sử dụng nó. đó chỉ là một ví dụ Đó là một lambda trước khi có cú pháp cho nó. Lambdas một cắt ngắn cho điều này.

Khép kín:

Lambda trở thành một bao đóng khi nó có thể truy cập các biến ngoài phạm vi này. Tôi đoán bạn có thể nói phép thuật của nó, nó kỳ diệu có thể bao quanh môi trường mà nó được tạo ra và sử dụng các biến bên ngoài phạm vi của nó (phạm vi bên ngoài. Vì vậy, rõ ràng, việc đóng cửa có nghĩa là lambda có thể truy cập OUTER SCOPE.

trong Kotlin, lambda luôn có thể truy cập vào bao đóng của nó (các biến nằm trong phạm vi bên ngoài của nó)


0

Nó phụ thuộc vào việc một hàm có sử dụng biến ngoài hay không để thực hiện thao tác.

Biến ngoài - biến được định nghĩa bên ngoài phạm vi của hàm.

  • Biểu thức Lambda không trạng thái vì nó phụ thuộc vào tham số, biến nội bộ hoặc hằng số để thực hiện các thao tác.

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Đóng trạng thái giữ vì nó sử dụng các biến bên ngoài (tức là biến được xác định bên ngoài phạm vi của thân hàm) cùng với các tham số và hằng số để thực hiện các hoạt động.

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

Khi Java tạo bao đóng, nó giữ biến n với hàm để nó có thể được tham chiếu khi được truyền cho các hàm khác hoặc được sử dụng ở bất cứ đâu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.