Kiedy używać znaku „(lub cytatu) w Lisp?

114

Po przejściu przez główne części wprowadzającej książki Lisp, nadal nie mogłem zrozumieć, co robi operator specjalny (quote)(lub równoważna ') funkcja, ale to było w całym kodzie Lispa, który widziałem.

Co to robi?

Cristián Romo
źródło

Odpowiedzi:

178

Krótka odpowiedź Pomijaj domyślne reguły oceny i nie oceniaj wyrażenia (symbol lub s-exp), przekazując je do funkcji dokładnie tak, jak wpisano.

Długa odpowiedź: domyślna reguła oceny

Kiedy wywoływana jest zwykła funkcja (do tego dojdę później), oceniane są wszystkie przekazane do niej argumenty. Oznacza to, że możesz napisać to:

(* (+ a 2)
   3)

Który z kolei oblicza (+ a 2), oceniając ai 2. Wartość symbolu ajest sprawdzana w bieżącym zestawie powiązań zmiennych, a następnie zastępowana. Say ajest obecnie powiązany z wartością 3:

(let ((a 3))
  (* (+ a 2)
     3))

Otrzymalibyśmy (+ 3 2)wtedy + jest wywoływane na 3 i 2 dając 5. Nasza pierwotna forma daje teraz (* 5 3)15.

Wyjaśnij quotejuż!

W porządku. Jak widać powyżej, wszystkie argumenty funkcji są oceniane, więc jeśli chcesz przekazać symbol, a a nie jego wartość, nie chcesz jej oceniać. Symbole Lisp mogą podwoić się zarówno jako ich wartości, jak i znaczniki tam, gdzie w innych językach używałbyś łańcuchów, takich jak klucze do tabel skrótów.

Tutaj quotepojawia się. Powiedzmy, że chcesz wykreślić alokacje zasobów z aplikacji Python, ale raczej wykonaj drukowanie w Lisp. Niech Twoja aplikacja Python zrobi coś takiego:

print("'(")
while allocating:
    if random.random() > 0.5:
        print(f"(allocate {random.randint(0, 20)})")
    else:
        print(f"(free {random.randint(0, 20)})")
    ...
print(")")

Dając wynik wyglądający tak (nieco ładnie):

'((allocate 3)
  (allocate 7)
  (free 14)
  (allocate 19)
  ...)

Pamiętasz, co powiedziałem quote(„zaznacz”), powodując, że domyślna reguła nie ma zastosowania? Dobry. Inaczej by się stało, gdyby wartości allocatei freebyły sprawdzane, a tego nie chcemy. W naszym Lispie chcemy:

(dolist (entry allocation-log)
  (case (first entry)
    (allocate (plot-allocation (second entry)))
    (free (plot-free (second entry)))))

Dla danych podanych powyżej zostałaby wykonana następująca sekwencja wywołań funkcji:

(plot-allocation 3)
(plot-allocation 7)
(plot-free 14)
(plot-allocation 19)

Ale o co chodzi list?

Cóż, czasem nie chce oceniać argumenty. Powiedzmy, że masz sprytną funkcję, która manipuluje liczbą i łańcuchem i zwraca listę wynikowych ... rzeczy. Zróbmy falstart:

(defun mess-with (number string)
  '(value-of-number (1+ number) something-with-string (length string)))

Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER (1+ NUMBER) SOMETHING-WITH-STRING (LENGTH STRING))

Hej! Nie tego chcieliśmy. Chcemy wybiórczo ocenić niektóre argumenty, a pozostałe zostawić jako symbole. Wypróbuj # 2!

(defun mess-with (number string)
  (list 'value-of-number (1+ number) 'something-with-string (length string)))

Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER 21 SOMETHING-WITH-STRING 3)

Nie tylko quote, alebackquote

Dużo lepiej! Nawiasem mówiąc, ten wzorzec jest tak powszechny w (głównie) makrach, że istnieje specjalna składnia służąca do tego. Cytat:

(defun mess-with (number string)
  `(value-of-number ,(1+ number) something-with-string ,(length string)))

To jak używanie quote, ale z opcją jawnej oceny niektórych argumentów, poprzedzając je przecinkiem. Wynik jest równoważny użyciu list, ale jeśli generujesz kod z makra, często chcesz ocenić tylko małe części zwróconego kodu, więc cudzysłów jest bardziej odpowiedni. W przypadku krótszych list listmoże być bardziej czytelny.

Hej, zapomniałeś o quote!

Więc gdzie to nas prowadzi? No tak, co quotewłaściwie robi? Po prostu zwraca argument (y) bez oceny! Pamiętasz, co powiedziałem na początku o zwykłych funkcjach? Okazuje się, że niektórzy operatorzy / funkcje muszą nie oceniać swoje argumenty. Na przykład IF - nie chciałbyś, aby gałąź else była oceniana, gdyby nie została podjęta, prawda? Tak zwane operatory specjalne , razem z makrami, działają w ten sposób. Operatory specjalne są również „aksjomatem” języka - minimalnym zestawem reguł - na podstawie których można zaimplementować resztę Lispa, łącząc je razem na różne sposoby.

Wróćmy quotejednak do:

Lisp> (quote spiffy-symbol)
SPIFFY-SYMBOL

Lisp> 'spiffy-symbol ; ' is just a shorthand ("reader macro"), as shown above
SPIFFY-SYMBOL

Porównaj z (w Steel-Bank Common Lisp):

Lisp> spiffy-symbol
debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD "initial thread" RUNNING   {A69F6A9}>:
  The variable SPIFFY-SYMBOL is unbound.

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-INT:SIMPLE-EVAL-IN-LEXENV SPIFFY-SYMBOL #<NULL-LEXENV>)
0] 

Ponieważ nie ma spiffy-symbolw obecnym zakresie!

Podsumowując

quote, backquote(z przecinkiem) i listsą to niektóre z narzędzi używanych do tworzenia list, które nie są tylko listami wartości, ale jak widać, mogą być używane jako lekkie (nie ma potrzeby definiowania a struct) struktur danych!

Jeśli chcesz dowiedzieć się więcej, polecam książkę Petera Seibela Practical Common Lisp, która zawiera praktyczne podejście do nauki języka Lisp, jeśli już zajmujesz się programowaniem. W końcu w swojej podróży do Lispa zaczniesz też używać pakietów. Rona Garret's The Idiot's Guide to Common Lisp Packages da ci dobre wyjaśnienie tych zagadnień.

Miłego hakowania!

Mikael Jansson
źródło
W moim emacsie SBCL jest skonfigurowany i kiedy wpiszę `` this '' jest `` true '', zwraca tylko ostatni, tj. PRAWDA na wyjściu. Nawet w portacle otrzymuję ten sam wynik
Totoro
@Totoro wartość zwracana funkcji lub po prostu wielu instrukcji w Lisp jest ostatni wyraz, więc to rzeczywiście powróci this, potem is, potem true, ale widzisz tylko ostatni zwrócony. (to i prawda są oddzielnymi oświadczeniami)
Wezl
52

Mówi „nie oceniaj mnie”. Na przykład, jeśli chcesz użyć listy jako danych, a nie kodu, umieść przed nią cytat. Na przykład,

(print '(+ 3 4))wypisuje "(+ 3 4)", natomiast (print (+ 3 4))wypisuje "7"

Adam Rosenfield
źródło
Jak można to ocenić, na przykład czy istnieje unquotepolecenie?
Lime
3
@William Lisps posiada wygodną funkcję o nazwie eval: (print (eval '(+ 3 4))). To właśnie sprawia, że ​​Lisps jest tak wspaniały: listy to kod, a kod to listy, więc program Lisp może manipulować sobą.
darkfeline
18

Inni ludzie wspaniale odpowiedzieli na to pytanie, a Matthias Benkard przedstawia doskonałe ostrzeżenie.

NIE UŻYWAJ CYTATU DO TWORZENIA LIST, KTÓRE ZMIENISZ PÓŹNIEJ. Specyfikacja pozwala kompilatorowi traktować cytowane listy jako stałe. Często kompilator optymalizuje stałe, tworząc dla nich pojedynczą wartość w pamięci, a następnie odwołując się do tej pojedynczej wartości ze wszystkich lokalizacji, w których występuje stała. Innymi słowy, może traktować stałą jak anonimową zmienną globalną.

Może to powodować oczywiste problemy. Jeśli zmodyfikujesz stałą, może to bardzo dobrze zmodyfikować inne zastosowania tej samej stałej w całkowicie niepowiązanym kodzie. Na przykład, możesz porównać jakąś zmienną do '(1 1) w jakiejś funkcji, aw zupełnie innej funkcji, zacznij listę od' (1 1), a następnie dodaj do niej więcej rzeczy. Po uruchomieniu tych funkcji może się okazać, że pierwsza funkcja nie jest już właściwie dopasowana, ponieważ teraz próbuje porównać zmienną z '(1 1 2 3 5 8 13), która jest tym, co zwróciła druga funkcja. Te dwie funkcje są zupełnie niepowiązane, ale mają na siebie wpływ ze względu na użycie stałych. Mogą się zdarzyć nawet bardziej szalone złe efekty, takie jak całkowicie normalna iteracja listy, nagle nieskończona pętla.

Użyj cytatu, gdy potrzebujesz stałej listy, na przykład do porównania. Użyj listy, gdy będziesz modyfikować wynik.

Xanthir
źródło
Więc wygląda na to, że powinieneś używać go przez (list (+ 1 2)) większość czasu. Jeśli tak, w jaki sposób można zapobiec ocenie (+ 1 2)wewnątrz takiego przykładu? Czy jest unquoterozkaz?
Lime
1
Czy chcesz odpowiednik '((3)), czy odpowiednik '((+ 1 2))? Jeśli to drugie, trzeba użyć więcej list: (list (list '+ 1 2)). Lub jeśli chcesz odpowiednika '(+ 1 2), po prostu (list '+ 1 2). I pamiętaj, jeśli nie modyfikujesz listy, możesz użyć cytatu: nie ma nic złego w tym '(+ 1 2), że po prostu porównujesz z tym czy coś.
Xanthir
1
Czy masz coś przeciwko odwołaniu się do miejsc, w których cytowane listy powinny być traktowane jako stałe?
Lime
HyperSpec clhs.lisp.se/Body/s_quote.htm mówi, że zachowanie jest niezdefiniowane, jeśli cytowany obiekt zostanie destrukcyjnie zmodyfikowany. Sugeruje się, że ma to na celu umożliwienie implikom traktowania wartości jako wartości atomowych.
Xanthir
14

Jedna z odpowiedzi na to pytanie mówi, że QUOTE „tworzy listy struktur danych”. To nie jest całkiem w porządku. CYTAT jest bardziej fundamentalny niż to. W rzeczywistości QUOTE jest trywialnym operatorem: jego celem jest zapobieganie wszelkim zdarzeniom. W szczególności niczego nie tworzy.

(QUOTE X) mówi po prostu „nic nie rób, po prostu daj mi X”. X nie musi być listą jak w (QUOTE (ABC)) ani symbolem jak w (QUOTE FOO). Może to być dowolny obiekt. Rzeczywiście, wynik oceny listy utworzonej przez (LISTA 'CZYTAJ CZĘŚĆ-PRZEDMIOT) zawsze zwróci po prostu SOME-OBJECT, cokolwiek to jest.

Otóż ​​powodem, dla którego (QUOTE (ABC)) wydaje się, że utworzył listę zawierającą elementy A, B i C, jest to, że taka lista naprawdę jest tym, co zwraca; ale w momencie oceny formularza QUOTE lista generalnie istnieje już od jakiegoś czasu (jako składnik formularza QUOTE!), utworzona przez program ładujący lub czytnik przed wykonaniem kodu.

Jedną z konsekwencji, która dość często potyka się z nowicjuszami, jest to, że bardzo nierozsądne jest modyfikowanie listy zwracanej przez formularz QUOTE. Dane zwracane przez QUOTE należy pod każdym względem traktować jako część wykonywanego kodu i dlatego należy je traktować jako tylko do odczytu!

Matthias Benkard
źródło
11

Cytat uniemożliwia wykonanie lub ocenę formularza, zamieniając go w dane. Ogólnie rzecz biorąc, możesz wykonać dane, następnie je oceniając.

quote tworzy listy struktur danych, na przykład poniższe są równoważne:

(quote a)
'a

Można go również użyć do tworzenia list (lub drzew):

(quote (1 2 3))
'(1 2 3)

Prawdopodobnie najlepiej będzie, jeśli kupisz książkę wprowadzającą na temat seplenienia, taką jak Practical Common Lisp (którą można przeczytać on-line).

Kyle Burton
źródło
3

W Emacs Lisp:

Co można cytować?

Listy i symbole.

Cytowanie liczby skutkuje samą liczbą: '5jest tym samym, co 5.

Co się dzieje, gdy cytujesz listy?

Na przykład:

'(one two) ocenia do

(list 'one 'two) która ocenia się do

(list (intern "one") (intern ("two"))).

(intern "one")tworzy symbol o nazwie „jeden” i przechowuje go na „centralnej” mapie skrótów, więc za każdym razem, gdy powiesz, 'onenazwany symbol "one"zostanie wyszukany na tej centralnej mapie.

Ale co to jest symbol?

Na przykład w językach OO (Java / Javascript / Python) symbol może być reprezentowany jako obiekt, który ma namepole, które jest nazwą symbolu jak "one"powyżej, a dane i / lub kod można powiązać z tym obiektem.

Zatem symbol w Pythonie można zaimplementować jako:

class Symbol:
   def __init__(self,name,code,value):
       self.name=name
       self.code=code
       self.value=value

Na przykład w Emacs Lisp symbol może mieć 1) skojarzone z nim dane ORAZ (w tym samym czasie - dla tego samego symbolu) 2) skojarzony z nim kod - w zależności od kontekstu wywoływane są dane lub kod.

Na przykład w Elisp:

(progn
  (fset 'add '+ )
  (set 'add 2)
  (add add add)
)

ocenia do 4.

Ponieważ (add add add)ocenia się jako:

(add add add)
(+ add add)
(+ 2 add)
(+ 2 2)
4

Na przykład, używając Symbolklasy, którą zdefiniowaliśmy powyżej w Pythonie, ten addELisp-Symbol można zapisać w Pythonie jako Symbol("add",(lambda x,y: x+y),2).

Wielkie dzięki dla ludzi na IRC #emacs za wyjaśnienie mi symboli i cytatów.

jhegedus
źródło
2

Kiedy chcemy przekazać sam argument zamiast przekazywać wartość argumentu, używamy cudzysłowu. Jest to głównie związane z przebiegiem procedury podczas korzystania z list, par i atomów, które nie są dostępne w języku programowania C (większość ludzi zaczyna programować w języku C, przez co jesteśmy zdezorientowani) To jest kod w języku programowania Scheme, który jest dialektem lisp i myślę, że możesz zrozumieć ten kod.

(define atom?              ; defining a procedure atom?
  (lambda (x)              ; which as one argument x
(and (not (null? x)) (not(pair? x) )))) ; checks if the argument is atom or not
(atom? '(a b c)) ; since it is a list it is false #f

Ostatnia linia (atom? 'Abc) przekazuje abc, tak jak ma to miejsce w procedurze sprawdzającej, czy abc jest atomem, czy nie, ale po przejściu (atom? Abc) sprawdza wartość abc i przekazuje wartość do to. Ponieważ nie nadaliśmy mu żadnej wartości

nieznany błąd
źródło
2
Code is data and data is code.  There is no clear distinction between them.

Jest to klasyczne stwierdzenie, które zna każdy programista.

Kiedy cytujesz kod, będą to dane.

1 ]=> '(+ 2 3 4)
;Value: (+ 2 3 4)

1 ]=> (+ 2 3 4)
;Value: 9

Cytując kod, wynikiem będą dane reprezentujące ten kod. Tak więc, gdy chcesz pracować z danymi, które reprezentują program, podajesz cytat z tego programu. Dotyczy to również wyrażeń atomowych, nie tylko list:

1 ]=> 'code
;Value: code

1 ]=> '10
;Value: 10

1 ]=> '"ok"
;Value: "ok"

1 ]=> code
;Unbound variable: code

Przypuśćmy, że chcesz stworzyć język programowania osadzony w lisp - będziesz pracować z programami, które są cytowane w schemacie (jak '(+ 2 3)) i które są interpretowane jako kod w języku, który tworzysz, poprzez nadanie programom interpretacji semantycznej. W takim przypadku musisz użyć cytatu, aby zachować dane, w przeciwnym razie zostaną one ocenione w języku zewnętrznym.

alinsoar
źródło
1

Cudzysłów zwraca wewnętrzną reprezentację swoich argumentów. Po przebiciu się przez zbyt wiele wyjaśnień tego, czego cytat nie robi, wtedy zapaliła się żarówka. Gdyby REPL nie konwertowało nazw funkcji na WIELKIE LITERY, kiedy je cytowałem, mogłoby to nie dotarło do mnie.

Więc. Zwykłe funkcje Lispa konwertują swoje argumenty na wewnętrzną reprezentację, oceniają argumenty i stosują funkcję. Cudzysłów konwertuje swoje argumenty na reprezentację wewnętrzną i po prostu zwraca to. Technicznie rzecz biorąc, słuszne jest stwierdzenie, że cytat mówi „nie oceniaj”, ale kiedy próbowałem zrozumieć, co to zrobiło, mówienie mi, czego nie robi, było frustrujące. Mój toster również nie ocenia funkcji Lispa; ale nie tak wyjaśniasz, co robi toster.

Steve
źródło
1

Jeszcze krótsza odpowiedź:

quoteoznacza bez oceny, a cytat odwrotny jest cytatem, ale pozostawia tylne drzwi .

Dobre odniesienie:

Podręcznik użytkownika Emacs Lisp wyjaśnia to bardzo jasno

9.3 Cytowanie

Specjalny cudzysłów w postaci zwraca pojedynczy argument w takiej postaci, w jakiej został zapisany, bez jego oceny. Zapewnia to sposób dołączania do programu stałych symboli i list, które nie są obiektami samooceniającymi się. (Nie jest konieczne cytowanie obiektów samooceniających, takich jak liczby, łańcuchy i wektory).

Forma specjalna: cytat obiekt

This special form returns object, without evaluating it. 

Ponieważ cytat jest tak często używany w programach, Lisp zapewnia wygodną dla niego składnię do odczytu. Znak apostrofu („”), po którym następuje obiekt Lisp (w składni do odczytu), rozwija się do listy, której pierwszym elementem jest cytat, a drugim elementem jest obiekt. Zatem składnia odczytu 'x jest skrótem od (cudzysłów x).

Oto kilka przykładów wyrażeń wykorzystujących cytat:

(quote (+ 1 2))
      (+ 1 2)

(quote foo)
      foo

'foo
      foo

''foo
      (quote foo)

'(quote foo)
      (quote foo)

9.4 Wycena wsteczna

Konstrukcje cudzysłowu wstecznego pozwalają cytować listę, ale wybiórczo oceniać elementy tej listy. W najprostszym przypadku jest identyczny ze specjalnym cytatem w formularzu (opisanym w poprzedniej sekcji; patrz Cytowanie). Na przykład te dwie formy dają identyczne wyniki:

`(a list of (+ 2 3) elements)
      (a list of (+ 2 3) elements)

'(a list of (+ 2 3) elements)
      (a list of (+ 2 3) elements)

Specjalny znacznik „,” wewnątrz argumentu odwrotnego cudzysłowu wskazuje wartość, która nie jest stała. Ewaluator Emacs Lisp ocenia argument „,” i umieszcza wartość w strukturze listy:

`(a list of ,(+ 2 3) elements)
      (a list of 5 elements)

Zastępowanie znakiem „,” jest również dozwolone na głębszych poziomach struktury listy. Na przykład:

`(1 2 (3 ,(+ 4 5)))
      (1 2 (3 9))

Obliczoną wartość można również połączyć z otrzymaną listą, używając specjalnego znacznika „, @”. Elementy listy składanej stają się elementami na tym samym poziomie, co inne elementy listy wynikowej. Odpowiednik kodu bez użycia znaku '' 'jest często nieczytelny. Oto kilka przykładów:

(setq some-list '(2 3))
      (2 3)

(cons 1 (append some-list '(4) some-list))
      (1 2 3 4 2 3)

`(1 ,@some-list 4 ,@some-list)
      (1 2 3 4 2 3)
Andrew_1510
źródło