6

Próbuję przekazać listę do funkcji w Lisp i zmienić zawartość tej listy w ramach funkcji bez wpływania na oryginalną listę. Czytałem, że Lisp jest wartością typu pass-by-value i to prawda, ale jest coś, co nie jest do końca zrozumiałe. Na przykład, ten kod działa zgodnie z oczekiwaniami:W typowym selektorze, jak zmodyfikować część parametru listy z poziomu funkcji bez zmiany oryginalnej listy?

(defun test() 
    (setf original '(a b c)) 
    (modify original) 
    (print original)) 
(defun modify (n) 
    (setf n '(x y z)) 
    n)

Jeśli zadzwonisz (test), drukuje (a b c) chociaż (modyfikacji) zwraca (X Y Z).

Jednak nie działa w ten sposób, jeśli spróbujesz zmienić tylko część listy. Zakładam, że ma to coś wspólnego z listami, które mają taką samą zawartość wszędzie w pamięci lub coś w tym stylu? Oto przykład:

(defun test() 
    (setf original '(a b c)) 
    (modify original) 
    (print original)) 
(defun modify (n) 
    (setf (first n) 'x) 
    n)

Następnie drukuje (test) (x b c). Jak więc zmienić niektóre elementy parametru listy w funkcji, tak jakby ta lista była lokalna dla tej funkcji?

+3

Należy pamiętać, że konsekwencje modyfikacji stałych literowych są niezdefiniowane. Nie rób tego. Nigdy. "(a b c) jest literalną stałą w kodzie. Nie należy go modyfikować. Możesz modyfikować listy tworzone za pomocą funkcji LIST (lista 'a' b 'c). –

+4

również zauważyć, że (SETF oryginał "(a b c)) nie ma sensu. SETF nie wprowadza zmiennych.zmienna "oryginał" nigdzie nie jest zdefiniowana. Możesz ustawić zmienne, które zostały wprowadzone przez LET, DEFUN, DEFVAR, DEFPARAMETER, ... –

Odpowiedz

7

Setf modyfikuje się. n może mieć miejsce. Pierwszy element listy n punkty mogą być również miejsce.

w obu przypadkach, wykaz posiadanych przez original jest przekazywana do modify jako parametr n. oznacza to, że zarówno original w funkcji test i n w funkcji modify trzymać teraz tej samej listy, który oznacza, że ​​zarówno original jak i n wskazują teraz swój pierwszy element.

Po tym, jak SETF zmieni n w pierwszym przypadku, nie będzie już wskazywać na tę listę, ale na nową listę. Nie ma to wpływu na listę wskazaną przez original. Nowa lista jest następnie zwracana przez modify, ale ponieważ ta wartość nie jest przypisana do niczego, znika ona z istnienia i wkrótce zostanie zbuforowana.

W drugim przypadku SETF modyfikuje nie n, ale wskazuje na pierwszy element listy n. Jest to ta sama lista, na którą wskazuje original, a następnie możesz zobaczyć zmodyfikowaną listę również za pomocą tej zmiennej. Aby uzyskać kopię listy, użyj COPY-LIST.

2

Prawdopodobnie masz problemy, ponieważ pomimo tego, że Lisp jest przekazywaną wartością odniesienia, odniesienia do obiektów są przekazywane, tak jak w Javie lub Pythonie. Twoje komórki cons zawierają referencje, które modyfikujesz, więc modyfikujesz zarówno oryginalne, jak i lokalne.

IMO, powinieneś spróbować napisać funkcje w bardziej funkcjonalnym stylu, aby uniknąć takich problemów. Mimo że Common Lisp jest wielo-paradygmatem, bardziej odpowiedni jest styl funkcjonalny.

(defun modyfikować (N) (x przeciw”(CDR n))

+0

To jest mój pierwszy projekt w seplenienie i jeszcze nie rozumiem programowania funkcjonalnego. Chyba muszę ponownie przemyśleć sposób, w jaki ułożyłem swój program? Próbuję sprawdzić potencjalne modyfikacje stanu gry bez modyfikowania stanu. Więc zamiast tworzyć funkcje do makowania i niezmienności, miałem nadzieję, że zamiast tego mogę zmodyfikować "kopię" stanu. – Ross

+0

Generalnie mądrze jest unikać niepotrzebnych zmian stanu. Często ich nie potrzebujesz i powodują, że programowanie jest trudniejsze do debugowania i prowadzi do takich błędów. – freiksenet

11

Listy Lisp oparte są na komórkach cons. Zmienne są jak wskaźniki do komórek cons (lub innych obiektów Lisp). Zmiana zmiennej nie zmieni innych zmiennych. Zmieniające się komórki są widoczne we wszystkich miejscach, w których znajdują się odniesienia do tych komórek.

Dobra książka to Touretzky, Common Lisp: A Gentle Introduction to Symbolic Computation.

Istnieje również oprogramowanie, które rysuje drzewa list i komórek.

Jeśli przekazać listę do funkcji takich jak to:

(modify (list 1 2 3)) 

Wtedy masz trzy różne sposoby korzystania z listy:

destrukcyjny modyfikację minusy komórek

(defun modify (list) 
    (setf (first list) 'foo)) ; This sets the CAR of the first cons cell to 'foo . 

udostępnianie struktury

(defun modify (list) 
    (cons 'bar (rest list))) 

Powyżej zwraca listę, która dzieli strukturę z podaną na liście: pozostałe elementy są takie same na obu listach.

kopiowanie

(defun modify (list) 
    (cons 'baz (copy-list (rest list)))) 

Powyższa funkcja BAZ jest podobna do baru, ale bez komórek list są wspólne, ponieważ lista jest kopiowany.

Nie trzeba dodawać, że często należy unikać destruktywnej modyfikacji, chyba że istnieje ku temu prawdziwy powód (np. Oszczędzanie pamięci, gdy jest tego warte).

Uwagi:

nigdy destrukcyjnie modyfikować dosłownych stałych list

Dont 'zrobić: (let ((L' (ABC))) (setf (pierwsza l) „bar))

Powód: lista może być zabezpieczony przed zapisem, albo mogą być współdzielone z innymi wykazami (ułożonych przez kompilator) itd

również:

Przedstaw zmiennych

jak ten

(let ((original (list 'a 'b 'c))) 
    (setf (first original) 'bar)) 

lub jak to

(defun foo (original-list) 
    (setf (first original-list) 'bar)) 

nigdy setf niezdefiniowanej zmiennej.

+0

Dziękuję. To mi bardzo pomogło. – Ross

4

to prawie taki sam, jak ten przykład C:

void modify1(char *p) { 
    p = "hi"; 
} 

void modify2(char *p) { 
    p[0] = 'h'; 
} 

w obu przypadkach wskaźnik jest przekazywana, w przypadku zmiany wskaźnika, jesteś zmieniając kopii parametru wartości wskaźnika (twierdzą, że na stos), jeśli zmienisz zawartość, zmienisz wartość dowolnego obiektu, który został wskazany.

Powiązane problemy