2012-01-22 28 views
15

Czy ktoś może wyjaśnić następujące zachowanie? W szczególności, dlaczego funkcja zwraca za każdym razem inną listę? Dlaczego nie jest some-list inicjowana na '(0 0 0) za każdym razem, gdy funkcja jest wywoływana?Dlaczego ta funkcja zwraca za każdym razem inną wartość?

(defun foo() 
    (let ((some-list '(0 0 0))) 
    (incf (car some-list)) 
    some-list)) 

wyjściowa:

> (foo) 
(1 0 0) 
> (foo) 
(2 0 0) 
> (foo) 
(3 0 0) 
> (foo) 
(4 0 0) 

Dzięki!

EDIT:

Ponadto, co jest zalecanym sposobem realizacji tej funkcji, zakładając, że chcę, aby funkcja wyjścia '(1 0 0) za każdym razem?

Odpowiedz

21

'(0 0 0) jest dosłownym obiekt, który zakłada się na stałym poziomie (aczkolwiek nie jest chroniona przed modyfikacjami). Więc za każdym razem skutecznie modyfikujesz ten sam obiekt. Aby utworzyć różne obiekty przy każdym wywołaniu funkcji, należy użyć (list 0 0 0).

Więc dopóki nie wiesz, co robisz, powinieneś zawsze używać literalnych list (jak '(0 0 0)) tylko jako stałych.

+0

Ach, teraz ma sens. Dzięki za jasne wyjaśnienie. –

+2

Prawdopodobnie byłoby miło dodać, że quasiquoting nie gwarantuje powrotu do nowych list. – 6502

+3

"Jeśli nie wiesz, co robisz" Zachowanie modyfikacji danych literalnych jest niezdefiniowane. Zgodnie ze specyfikacją, nie możesz właściwie wiedzieć, co robisz (z pewnością), więc "powinieneś ** zawsze ** używać literalnych list (jak '(0 0 0)) tylko jako stałych". –

-5

chcielibyście napisać sam, ale znalazłem dobry ONLINE:

Common Lisp posiada funkcje pierwszej klasy, to znaczy funkcje są obiektami, które mogą być tworzone w czasie wykonywania, i przekazywane jako argumenty do innych funkcji. --AlainPicard Te pierwszorzędne funkcje również mają swój własny stan, więc są funktorami. Wszystkie funkcje Lisp są funktorami; nie ma separacji między funkcjami "tylko kodowanymi" i "obiektami ". Stan przyjmuje postać przechwyconych wiązań zmiennych leksykalnych . Nie musisz używać LAMBDA do przechwytywania wiązań; defun najwyższego poziomu może to zrobić za: (let ((prywatnego zmienna 42)) (defun foo() ...))

Kod w miejscu ... widzi prywatnego zmienna w jego zakresie leksykalnym . Istnieje jedna instancja tej zmiennej powiązana z jednym obiektem funkcjonalnym globalnie powiązanym z symbolem FOO; zmienna jest przechwytywana w momencie oceny wyrażenia DEFUN. Ta zmienna zachowuje się jak zmienna statyczna w C. Lub, naprzemiennie, możesz myśleć o FOO jako obiekcie "singleton" z "instancją zmienną". --KazKylheku

Nr http://c2.com/cgi/wiki?CommonLisp

+1

Czy możesz wyjaśnić, w jaki sposób cytowany tekst odnosi się do pytania? Może czegoś brakuje, ale nie widzę tego. – sepp2k

+0

Tekst wyjaśnia, w jaki sposób funkcje są pierwszymi obiektami w Lisp i faktycznie mają "stan". Zadeklarowana zmienna była częścią "stanu" funkcji. Jak wyjaśnia tekst, jest to bardzo podobne do deklarowania statycznych zmiennych lokalnych w C. Jaka część tekstu nie jest związana z tym problemem? – xtrem

+2

Część, w której w ogóle nie jest to, co się dzieje. Twoja oferta dotyczy "przechwyconych leksykalnych wiązań zmiennych". Jednak 'some-list' jest lokalną zmienną' foo', nie jest przechwyconą zmienną, a zatem nie jest częścią stanu 'foo'. Przy każdym wywołaniu 'foo',' some-list' będzie miało unikalne powiązanie (które, jak wyjaśnił Vsevolod, będzie wskazywać na tę samą "stałą" listę, co wyjaśnia zachowanie OP). Jest to całkowicie odmienne od funkcji modyfikującej przechwycone zmienne. – sepp2k

9

Na marginesie, określając tę ​​funkcję w REPL SBCL można uzyskać następujące ostrzeżenie:

caught WARNING: 
    Destructive function SB-KERNEL:%RPLACA called on constant data. 
    See also: 
     The ANSI Standard, Special Operator QUOTE 
     The ANSI Standard, Section 3.2.2.3 

co daje dobrą wskazówkę do problemu pod ręką.

4

'(0 0 0) w kodzie jest dosłowne dane. Modyfikowanie tych danych ma niezdefiniowane zachowanie. Typowe implementacje Lisp mogą go nie wykryć w czasie wykonywania (chyba że dane są na przykład umieszczane w pamięci przeznaczonej tylko do odczytu). Ale może mieć niepożądane skutki.

  • widać, że dane te mogą być (i często jest) współużytkowane przez różne wywołania tej samej funkcji

  • jednym z bardziej subtelnych możliwe błędy to: Common Lisp zostało zdefiniowane różne optymalizacje co może być zrobione z myślą o kompilatorze. Na przykład, kompilator może ponownie wykorzystać dane:

przykład:

(let ((a '(1 2 3)) 
     (b '(1 2 3))) 
    (list a b)) 

podano fragment kodu kompilator może wykryć, że dosłownym dane a i b jest EQUAL. Może wtedy obie zmienne wskazują te same dane literalne. Modyfikowanie go może działać, ale zmiana jest widoczna z a i b.

Podsumowanie: Modyfikacja danych dosłownych jest źródłem kilku subtelnych błędów. Unikaj go, jeśli to możliwe. Następnie musisz cons nowych obiektów danych. Biorąc pod uwagę ogólnie oznacza alokację świeżych, nowych struktur danych w czasie wykonywania.

Powiązane problemy