2012-03-01 16 views
6

Widzę następujący kod ... Pierwsze wywołanie (next-num) zwraca 1, a drugie zwraca 2.Zmienna w funkcji

(define next-num 
    (let ((num 0)) 
    (lambda() (set! num (+ num 1)) num))) 

(next-num) ; 1 
(next-num) ; 2 

Co nie mogę zrozumieć ... num jest tworzony przez let wewnątrz next-num, jest to rodzaj zmiennej lokalnej ... Jak działa system wie, że za każdym razem next-num nazywa wartość num jest nie usunięte przez let ((num 0)); W jaki sposób system wie, że zawsze jest to ten sam kod, który modyfikujemy, gdy wywoływana jest nazwa next-num?

Wygląda na to, że num jest zarówno lokalny, jak i statyczny ... Jak zdefiniować zmienną lokalną, ale nie statyczną?

Odpowiedz

9

To jest "zamknięcie leksykalne" i masz rację, że num, "zmienna zamknięta" jest podobna do zmiennej statycznej, na przykład w C: jest widoczna tylko dla kodu w postaci let (jej "leksykalny" scope "), ale trwa przez cały czas trwania programu, a nie jest ponownie inicjowany przy każdym wywołaniu funkcji.

Wydaje mi się, że część, o której się mylisz, brzmi: "num jest tworzony przez domenę next-num, jest rodzajem zmiennej lokalnej". Nie jest to prawdą, ponieważ blok let nie jest częścią funkcji next-num: w rzeczywistości jest to wyrażenie, które tworzy i zwraca funkcję, która jest następnie powiązana z next-num. (Jest to bardzo odmienne, np. Od C, gdzie funkcje mogą być tworzone tylko podczas kompilacji i poprzez definiowanie ich na najwyższym poziomie.W Schematach funkcje są wartościami takimi jak liczby całkowite lub listy, które każde wyrażenie może zwrócić).

Oto kolejny sposób napisać (prawie) to samo, co sprawia, że ​​jaśniejsze że define właśnie skojarzenie next-num do wartości wyrażenia funkcyjnego-powrót:

(define next-num #f) ; dummy value 
(let ((num 0)) 
    (set! next-num 
     (lambda() (set! num (+ num 1)) num))) 

Ważne jest, aby zwrócić uwagę na różnicę pomiędzy

(define (some-var args ...) expression expression ...) 

co sprawia some-var funkcję, która wykonuje wszystkie expressions po nazwie, a

(define some-var expression) 

, która wiąże some-var z wartością expression, ocenianą wtedy i tam. Ściśle mówiąc, byłego wersja jest niepotrzebne, ponieważ jest to równoznaczne z

(define some-var 
    (lambda (args ...) expression expression ...)) 

kod jest prawie taki sam, jak ten, z dodatkiem zawężona leksykalnie zmiennej, num wokół postaci lambda.

Wreszcie, tutaj jest kluczowa różnica między zmiennymi zamkniętymi a statycznymi, co sprawia, że ​​zamknięcia są znacznie potężniejsze.Gdybyś napisał następujące zamiast:

(define make-next-num 
    (lambda (num) 
    (lambda() (set! num (+ num 1)) num))) 

wtedy każde wywołanie make-next-num stworzyłoby anonimową funkcję z nowym, odrębnym num zmiennej, która jest prywatny do tej funkcji:

(define f (make-next-num 7)) 
(define g (make-next-num 2)) 

(f) ; => 8 
(g) ; => 3 
(f) ; => 9 

To jest naprawdę fajna i potężna sztuczka, która tłumaczy moc języków z leksykalnymi zamknięciami.

Edytowane w celu dodania: Pytasz, jak Schemat "wie", który num modyfikuje po wywołaniu next-num. W zarysie, jeśli nie w realizacji, jest to całkiem proste. Każde wyrażenie na Schemacie jest oceniane w kontekście środowiska (tabeli odnośników) zmiennych powiązań, które są skojarzeniami nazw z miejscami, w których można przechowywać wartości. Każda ocena formularza let lub wywołania funkcji tworzy nowe środowisko, rozszerzając bieżące środowisko o nowe powiązania. Aby formularze miały postać zamknięć, implementacja reprezentuje je jako strukturę składającą się z samej funkcji oraz środowiska, w którym została zdefiniowana. Wywołania tej funkcji są następnie analizowane przez rozszerzenie środowiska powiązania, w którym zdefiniowano tę funkcję - , a nie środowiska, w którym została wywołana.

Starsze Lispy (w tym Emacs Lisp do niedawna) miały lambda, ale nie zakres leksykalny, więc chociaż można było tworzyć funkcje anonimowe, połączenia z nimi byłyby oceniane w środowisku wywołującym, a nie w środowisku definicji, a więc nie było żadnych zamknięcia. Uważam, że Scheme był pierwszym językiem, który to poprawił. Oryginalna wersja Sussmana i Steele'a Lambda Papers dotycząca realizacji Schematu sprawia, że ​​lektura rozwijająca umysł jest świetna dla każdego, kto chce zrozumieć scoping i wiele innych rzeczy.