Jaka jest różnica między eq ?, eqv ?, equal? ​​I = in Scheme?

85

Zastanawiam się, jaka jest różnica między tymi operacjami w Scheme. Widziałem podobne pytania w Stack Overflow, ale dotyczą Lispa i nie ma porównania między trzema z tych operatorów.

Piszę różne typy poleceń w schemacie i otrzymuję następujące dane wyjściowe:

(eq? 5 5) -->#t
(eq? 2.5 2.5) -->#f
(equal? 2.5 2.5) --> #t
(= 2.5 2.5) --> #t

Dlaczego tak się dzieje?

yrazlik
źródło
3
i jest też eqv?, co oznacza coś innego niż eq?lubequal?
nowość

Odpowiedzi:

155

Odpowiem na to pytanie stopniowo. Zacznijmy od =predykatu równoważności. =Orzecznik służy do sprawdzania, czy dwie liczby są równe. Jeśli podasz cokolwiek innego niż liczbę, spowoduje to błąd:

(= 2 3)     => #f
(= 2.5 2.5) => #t
(= '() '()) => error

eq?Orzecznik służy do sprawdzenia, czy jej dwa parametry respresent tego samego obiektu w pamięci. Na przykład:

(define x '(2 3))
(define y '(2 3))
(eq? x y)         => #f
(define y x)
(eq? x y)         => #t

Zauważ jednak, że '()w pamięci jest tylko jedna pusta lista (w rzeczywistości pusta lista nie istnieje w pamięci, ale wskaźnik do lokalizacji pamięci 0jest uważany za pustą listę). Stąd przy porównywaniu pustych list eq?zawsze zwróci #t(ponieważ reprezentują ten sam obiekt w pamięci):

(define x '())
(define y '())
(eq? x y)      => #t

Teraz, w zależności od implementacji, eq?może zwracać lub nie zwracać #twartości pierwotnych, takich jak liczby, łańcuchy itp. Na przykład:

(eq? 2 2)     => depends upon the implementation
(eq? "a" "a") => depends upon the implementation

Tutaj pojawia się eqv?predykat. eqv?Jest dokładnie taka sama jak eq?orzecznika, chyba że będzie to zawsze powrócić #tdo tych samych wartościach pierwotnych. Na przykład:

(eqv? 2 2)     => #t
(eqv? "a" "a") => depends upon the implementation

W związku z tym eqv?jest nadzbiorem eq?i w większości przypadków należy używać eqv?zamiast eq?.

Wreszcie dochodzimy do equal?orzeczenia. equal?Orzecznikiem jest dokładnie taka sama jak eqv?orzecznika, oprócz tego, że może być on również używany do badania, czy dwie listy, wektory, itd. Mają odpowiednie elementy, które spełniają eqv?predykat. Na przykład:

(define x '(2 3))
(define y '(2 3))
(equal? x y)      => #t
(eqv? x y)        => #f

Ogólnie:

  1. Użyj =predykatu, jeśli chcesz sprawdzić, czy dwie liczby są równoważne.
  2. Użyj eqv?predykatu, jeśli chcesz sprawdzić, czy dwie wartości nienumeryczne są równoważne.
  3. Użyj equal?predykatu, jeśli chcesz sprawdzić, czy dwie listy, wektory itp. Są równoważne.
  4. Nie używaj eq?predykatu, chyba że dokładnie wiesz, co robisz.
Aadit M Shah
źródło
7
AFAIK (eqv? "a" "a") ==> unspecified. Będziesz musiał użyć equal?lub (prawdopodobnie bardziej zoptymalizowanego)string=?
Sylwester
3
według raportu , (eq? '(1) '(1))jest nieokreślona , więc (define x '(1 2))ilustracja może nie działać.
Will Ness,
4
Bardzo dokładne i pouczające. Zwłaszcza wskazówki na końcu.
Germán Diago
2
Ale eq? wydaje się być zdefiniowany dla symboli i należy to zauważyć! Jeśli symbole wyglądają tak samo, eq? zwraca #t. Przykład (eq? 'foo 'foo) -> #t, (eq? 'foo 'bar)-> false`. Czytam to tutaj i tutaj
Nedko
13

W specyfikacji RnRS znajdują się pełne dwie strony dotyczące eq?, eqv?, equal? and =. Oto wersja robocza specyfikacji R7RS . Sprawdź to!

Wyjaśnienie:

  • = porównuje liczby, 2,5 i 2,5 są liczbowo równe.
  • equal?dla liczb zmniejsza się do =, 2,5 i 2,5 są liczbowo równe.
  • eq?porównuje „wskaźniki”. Liczba 5 w Twojej implementacji Planu jest zaimplementowana jako „natychmiastowa” (prawdopodobnie), a zatem 5 i 5 są identyczne. Liczba 2.5 może wymagać alokacji „rekordu zmiennoprzecinkowego” w twojej implementacji Schematu, dwa wskaźniki nie są identyczne.
GoZoner
źródło
1
Link do wersji roboczej specyfikacji R7RS jest martwy od 04.02.2018
Jeremiah Peschka
2
Zaktualizowano do aktywnego łącza.
GoZoner,
10

eq?to #tkiedy jest to ten sam adres / przedmiot. Normalnie można by oczekiwać #t dla tego samego symbolu, wartości logicznej i obiektu oraz #f dla wartości innego typu, z różnymi wartościami lub inną strukturą Implementacje schematu / Lispa mają tradycję osadzania typu we wskaźnikach i osadzania wartości w tej samej przestrzeni, jeśli jest wystarczająco dużo miejsca. Dlatego niektóre wskaźniki tak naprawdę nie są adresami, ale wartościami, jak znak Rlub Fixnum 10. Będą tak, eq?ponieważ „adres” to typ osadzony + wartość. Niektóre implementacje również ponownie wykorzystują niezmienne stałe. (eq? '(1 2 3)' (1 2 3)) może mieć wartość #f podczas interpretacji, ale #t podczas kompilacji, ponieważ może uzyskać ten sam adres. (Podobnie jak stała pula String w Javie). Z tego powodu wiele wyrażeń dotyczyeq? są nieokreślone, więc to, czy szacuje się jako #t lub #f, zależy od implementacji.

eqv?są #t za te same rzeczy, co eq?. Jest również #t, jeśli jest to liczba lub znak, a jego wartość jest taka sama , nawet jeśli dane są zbyt duże, aby zmieścić się we wskaźniku. W tym eqv?przypadku dodatkowa praca polega na sprawdzeniu, czy typ jest jednym z obsługiwanych, czy oba są tego samego typu, a obiekty docelowe mają tę samą wartość danych.

equal?jest #t dla tych samych rzeczy, co eqv?i jeśli jest to typ złożony, taki jak para, wektor, ciąg i wektor bajtowy, to rekurencyjnie robi to equal?z częściami. W praktyce zwróci #t, jeśli dwa obiekty będą wyglądać tak samo . Przed wersją R6RS używanie equal?na konstrukcjach okrągłych jest niebezpieczne .

=jest podobne, eqv?ale działa tylko dla typów numerycznych . To mogłoby być bardziej wydajne.

string=?jest jak equal?, ale działa tylko dla stringów. To mogłoby być bardziej wydajne.

Sylwester
źródło
6

equal? rekurencyjnie porównuje dwa obiekty (dowolnego typu) pod kątem równości.

  • Zauważ, że może to być kosztowne w przypadku dużej struktury danych, ponieważ potencjalnie trzeba przejść przez całą listę, ciąg, wektor itp.

  • Jeśli obiekt zawiera tylko jeden element (np. Numer, znak itp.), Jest to to samo, co eqv?.


eqv? testuje dwa obiekty, aby określić, czy oba są „normalnie traktowane jako ten sam obiekt”.

  • eqv?i eq?są bardzo podobnymi operacjami, a różnice między nimi będą w pewnym stopniu specyficzne dla implementacji.

eq?jest tym samym, eqv?ale może być w stanie dostrzec subtelniejsze rozróżnienia i może być wdrażane skuteczniej.

  • Zgodnie ze specyfikacją można to zaimplementować jako szybkie i wydajne porównanie wskaźników, w przeciwieństwie do bardziej skomplikowanej operacji dla eqv?.


= porównuje liczby dla równości liczbowej.

  • Pamiętaj, że można podać więcej niż dwie liczby, np .: (= 1 1.0 1/1 2/2)
Justin Ethier
źródło
Myślałem, że eq?to rzeczywista równość wskaźnika (nie eqv?). To jest „najlepsze lub najbardziej wybredne”. Np. (eqv? 2 2)Jest gwarantowane #t, ale (eq? 2 2)jest „nieokreślone”. To znaczy, zależy to od tego, czy implementacja tworzy rzeczywisty nowy obiekt pamięci dla każdego nowo odczytanego numeru, czy też ponownie wykorzystuje wcześniej utworzony, jeśli to możliwe.
Will Ness,
@WillNess - Dobry chwyt, dzięki. Różnice między eq?i eqv?są bardziej subtelne niż w przypadku innych operacji.
Justin Ethier,
5

Nie wspominasz o implementacji schematu, ale w Racket eq?zwraca prawdę tylko wtedy, gdy argumenty odnoszą się do tego samego obiektu. Twój drugi przykład daje #f, ponieważ system tworzy nową liczbę zmiennoprzecinkową dla każdego argumentu; nie są tym samym obiektem.

equal?i =sprawdzają równoważność wartości, ale =ma zastosowanie tylko do liczb.

Jeśli używasz rakiety, sprawdź tutaj, aby uzyskać więcej informacji. W przeciwnym razie sprawdź dokumentację dotyczącą wdrożenia schematu.

Alan Gilbert
źródło
3
Jeszcze lepiej ... Przeczytaj specyfikację ... r6rs.org/final/html/r6rs/r6rs-ZH-14.html#node_sec_11.5
Dirk
3

Pomyśl o eq?równości wskaźnika. Autorzy raportu chcą, aby był jak najbardziej ogólny, więc nie mówią tego wprost, ponieważ jest on zależny od implementacji, a mówiąc to, faworyzowaliby implementacje oparte na wskaźnikach. Ale mówią

Zwykle będzie można zaimplementować eq? znacznie wydajniej niż eqv ?, na przykład jako proste porównanie wskaźników

Oto co mam na myśli. (eqv? 2 2)jest gwarantowany, #tale nie (eq? 2 2)jest określony. Teraz wyobraź sobie implementację opartą na wskaźnikach. W tym eq?jest tylko porównanie wskaźników. Ponieważ nie (eq? 2 2)jest określony, oznacza to, że ta implementacja może po prostu utworzyć nową reprezentację obiektu pamięci dla każdej nowej liczby odczytywanej z kodu źródłowego. eqv?musi faktycznie zbadać jej argumenty.

OTOH (eq 'a 'a)jest #t. Oznacza to, że taka realizacja musi rozpoznać symbole o takich samych nazwach i wykorzystują ten sam jeden obiekt reprezentacja w pamięci dla wszystkich z nich.

Załóżmy, że implementacja nie jest oparta na wskaźnikach. Dopóki jest to zgodne z raportem, nie ma to znaczenia. Autorzy po prostu nie chcą być postrzegani jako dyktujący specyfikę implementacji wykonawcom, więc starannie dobierają ich sformułowania.

To i tak jest moje przypuszczenie.

Tak więc bardzo z grubsza, eq?jest równość wskaźnika, eqv?jest (atomowa-) świadoma wartości, equal?jest również świadoma struktury (rekurencyjnie sprawdza swoje argumenty, więc ostatecznie (equal? '(a) '(a))jest wymagana #t), =dotyczy liczb, string=?jest dla łańcuchów, a szczegóły są w raporcie.

Will Ness
źródło
0

Oprócz poprzednich odpowiedzi dodam kilka komentarzy.

Wszystkie te predykaty chcą zdefiniować abstrakcyjną funkcję identityobiektu, ale w różnych kontekstach.

EQ?jest zależne od implementacji i nie odpowiada na pytanie are 2 objects the same?tylko w ograniczonym użyciu. Z punktu widzenia implementacji, ten predykat porównuje tylko 2 liczby (wskaźnik do obiektów), nie patrzy na zawartość obiektów. Na przykład, jeśli Twoja implementacja nie przechowuje unikalnie łańcuchów wewnątrz, ale alokuje inną pamięć dla każdego ciągu, to (eq? "a" "a")będzie fałszywa.

EQV?- to wygląda wewnątrz obiektów, ale przy ograniczonym użyciu. Jest zależne od implementacji, jeśli zwraca true dla (eqv? (lambda(x) x) (lambda(x) x)). Tutaj jest pełna filozofia, jak zdefiniować ten predykat, ponieważ obecnie wiemy, że istnieją szybkie metody porównywania funkcjonalności niektórych funkcji o ograniczonym zastosowaniu. Ale eqv?zapewnia spójną odpowiedź na duże liczby, łańcuchy itp.

Praktycznie niektóre z tych predykatów próbują użyć abstrakcyjnej definicji obiektu (matematycznie), podczas gdy inne używają reprezentacji obiektu (jak jest to zaimplementowane na prawdziwej maszynie). Matematyczna definicja tożsamości pochodzi od Leibniza i mówi:

X = Y  iff  for any P, P(X) = P(Y)
X, Y being objects and
P being any property associated with object X and Y.

Idealnie byłoby, gdyby można było zaimplementować tę samą definicję na komputerze, ale ze względu na nierozstrzygalność i / lub szybkość nie jest ona implementowana dosłownie. Dlatego jest wielu operatorów, którzy próbują skupić się na różnych punktach widzenia związanych z tą definicją.

Spróbuj wyobrazić sobie abstrakcyjną definicję tożsamości jako kontynuacji. Nawet jeśli możesz podać definicję podzbioru funkcji ( rekurencyjna klasa funkcji sigma ), język nie narzuca żadnego predykatu, aby był prawdziwy lub fałszywy. Bardzo skomplikowałoby to zarówno definicję języka, jak i znacznie bardziej implementację.

Kontekst innych predykatów jest łatwiejszy do analizy.

alinsoar
źródło