Đây là "đóng cửa từ vựng" và bạn đúng là num
, "biến đóng trên" tương tự như biến tĩnh, trong C ví dụ: chỉ hiển thị mã trong biểu mẫu let
phạm vi "), nhưng nó vẫn tồn tại trên toàn bộ chương trình chạy, thay vì được khởi tạo lại với mỗi cuộc gọi đến hàm.
Tôi nghĩ rằng phần bạn đang bối rối là: "num
được tạo bởi cho phép bên trong next-num
, nó là loại biến cục bộ". Điều này không đúng bởi vì khối let
không phải là một phần của hàm next-num
: đó thực sự là một biểu thức tạo và trả về hàm sau đó được liên kết với next-num
. (Điều này rất khác, ví dụ, từ C, trong đó các hàm chỉ có thể được tạo tại thời gian biên dịch và bằng cách định nghĩa chúng ở mức cao nhất. Trong Đề án, các hàm là các giá trị như số nguyên hoặc danh sách, bất kỳ biểu thức nào có thể trả về).
Dưới đây là một cách khác để viết (gần như) điều tương tự mà làm cho nó rõ ràng hơn rằng define
chỉ được liên kết next-num
với giá trị của một biểu thức hàm trả về:
(define next-num #f) ; dummy value
(let ((num 0))
(set! next-num
(lambda() (set! num (+ num 1)) num)))
Điều quan trọng cần lưu ý sự khác biệt giữa
(define (some-var args ...) expression expression ...)
mà làm some-var
một chức năng mà thực thi tất cả expressions
khi gọi, và
(define some-var expression)
liên kết some-var
với giá trị expression
, được đánh giá rồi đến đó. Nói đúng ra, các phiên bản cũ là không cần thiết, bởi vì nó tương đương với
(define some-var
(lambda (args ...) expression expression ...))
Mã của bạn là gần như giống nhau như thế này, với việc bổ sung các biến lexically scoped, num
, xung quanh hình thức lambda
.
Cuối cùng, đây là điểm khác biệt chính giữa các biến đóng và biến tĩnh, làm cho các bao đóng mạnh hơn rất nhiều.Nếu bạn đã viết những điều sau đây thay vì:
(define make-next-num
(lambda (num)
(lambda() (set! num (+ num 1)) num)))
sau đó mỗi cuộc gọi đến make-next-num
sẽ tạo ra một chức năng ẩn danh với một mới, khác biệt num
biến, đó là tới chức năng:
(define f (make-next-num 7))
(define g (make-next-num 2))
(f) ; => 8
(g) ; => 3
(f) ; => 9
Đây là một lừa thực sự thú vị và mạnh mẽ, chiếm rất nhiều sức mạnh của ngôn ngữ với các đóng cửa từ vựng.
Đã chỉnh sửa để thêm: Bạn hỏi cách Scheme "biết" num
nào để sửa đổi khi gọi next-num
. Trong phác thảo, nếu không thực hiện, điều này thực sự khá đơn giản. Mỗi biểu thức trong Đề án được đánh giá trong ngữ cảnh của một môi trường (bảng tra cứu) của các ràng buộc biến, là các kết hợp của các tên tới các địa điểm có thể chứa các giá trị. Mỗi đánh giá của một hình thức let
hoặc một cuộc gọi chức năng tạo ra một môi trường mới bằng cách mở rộng môi trường hiện tại với các ràng buộc mới. Để sắp xếp để có các biểu mẫu lambda
hoạt động như các bao đóng, việc triển khai biểu diễn chúng dưới dạng một cấu trúc bao gồm chính hàm đó cộng với môi trường mà nó được xác định. Các cuộc gọi đến hàm đó sau đó được đánh giá bằng cách mở rộng môi trường ràng buộc trong đó hàm được xác định - không phải là môi trường mà nó được gọi.
Lisps cũ (bao gồm Emacs Lisp cho đến gần đây) có lambda
, nhưng không phải là phạm vi từ vựng, vì vậy mặc dù bạn có thể tạo các hàm ẩn danh, cuộc gọi đến chúng sẽ được đánh giá trong môi trường gọi điện chứ không phải môi trường định nghĩa. đóng cửa. Tôi tin rằng Scheme là ngôn ngữ đầu tiên để có được quyền này. Sussman và Steele ban đầu của Lambda Papers về việc thực hiện Đề án làm cho đọc lớn tâm trí mở rộng cho bất cứ ai muốn hiểu phạm vi, trong số nhiều thứ khác.