Qual è la differenza tra eq ?, eqv ?, equal? ​​E = in Scheme?


87

Mi chiedo quale sia la differenza tra queste operazioni in Scheme. Ho visto domande simili in Stack Overflow ma riguardano Lisp e non c'è un confronto tra tre di questi operatori.

Sto scrivendo i diversi tipi di comandi in Scheme e ottengo i seguenti output:

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

Perché è così?


3
e c'è anche eqv?, il che significa qualcosa di diverso da eq?oequal?
newacct

Risposte:


160

Risponderò a questa domanda in modo incrementale. Cominciamo con il =predicato di equivalenza. Il =predicato viene utilizzato per verificare se due numeri sono uguali. Se fornisci qualcos'altro tranne un numero, verrà generato un errore:

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

Il eq?predicato viene utilizzato per verificare se i suoi due parametri rappresentano lo stesso oggetto in memoria. Per esempio:

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

Nota tuttavia che c'è solo un elenco vuoto '()in memoria (in realtà l'elenco vuoto non esiste in memoria, ma un puntatore alla posizione di memoria 0è considerato come l'elenco vuoto). Pertanto, quando si confrontano elenchi vuoti, eq?verranno sempre restituiti #t(perché rappresentano lo stesso oggetto in memoria):

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

Ora, a seconda dell'implementazione, eq?può o meno restituire #tvalori primitivi come numeri, stringhe, ecc. Ad esempio:

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

È qui che eqv?entra in gioco il predicato. Il eqv?è esattamente lo stesso del eq?predicato, tranne per il fatto che restituirà sempre gli #tstessi valori primitivi. Per esempio:

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

Quindi eqv?è un superset di eq?e per la maggior parte dei casi dovresti usare al eqv?posto di eq?.

Infine veniamo al equal?predicato. Il equal?predicato è esattamente lo stesso del eqv?predicato, tranne per il fatto che può anche essere utilizzato per verificare se due elenchi, vettori, ecc. Hanno elementi corrispondenti che soddisfano il eqv?predicato. Per esempio:

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

In generale:

  1. Usa il =predicato quando desideri verificare se due numeri sono equivalenti.
  2. Usa il eqv?predicato quando desideri verificare se due valori non numerici sono equivalenti.
  3. Usa il equal?predicato quando desideri verificare se due elenchi, vettori, ecc. Sono equivalenti.
  4. Non usare il eq?predicato a meno che tu non sappia esattamente cosa stai facendo.

7
AFAIK (eqv? "a" "a") ==> unspecified. Dovrai usare equal?o (forse il più ottimizzato)string=?
Sylwester

3
secondo il Rapporto , non(eq? '(1) '(1)) è specificato , quindi la tua (define x '(1 2))illustrazione potrebbe non funzionare.
Will Ness

4
Molto accurato e informativo. Soprattutto le linee guida alla fine.
Germán Diago

2
Ma eq? sembra essere definito per i simboli e questo dovrebbe essere notato! Se i simboli hanno lo stesso aspetto, eq? restituisce #t. Esempio (eq? 'foo 'foo) -> #t, (eq? 'foo 'bar)-> false`. L'ho letto qui e qui
Nedko

13

Ci sono due pagine complete nella specifica RnRS relativa a eq?, eqv?, equal? and =. Ecco la bozza delle specifiche R7RS . Controlla!

Spiegazione:

  • = confronta i numeri, 2,5 e 2,5 sono numericamente uguali.
  • equal?per i numeri si riduce a =, 2.5 e 2.5 sono numericamente uguali.
  • eq?confronta i "puntatori". Il numero 5, nell'implementazione dello schema, è implementato come "immediato" (probabile), quindi 5 e 5 sono identici. Il numero 2.5 può richiedere un'allocazione di un "record in virgola mobile" nell'implementazione dello schema, i due puntatori non sono identici.

1
Il collegamento alla bozza delle specifiche R7RS è morto a partire dal 2018-02-04
Jeremiah Peschka

2
Aggiornato a un collegamento live.
GoZoner

10

eq?è #tquando è lo stesso indirizzo / oggetto. Normalmente ci si potrebbe aspettare #t per lo stesso simbolo, booleano e oggetto e #f per valori di tipo diverso, con valori diversi, o non la stessa struttura Scheme / Lisp-implementations ha una tradizione di incorporare il tipo nei loro puntatori e di incorporare valori nello stesso spazio se lo spazio è sufficiente. Quindi alcuni puntatori in realtà non sono indirizzi ma valori, come char Ro Fixnum 10. Ciò sarà eq?poiché l '"indirizzo" è un valore di tipo incorporato +. Alcune implementazioni riutilizzano anche costanti immutabili. (eq? '(1 2 3)' (1 2 3)) potrebbe essere #f quando interpretato ma #t quando compilato poiché potrebbe ottenere lo stesso indirizzo. (Come il pool di stringhe costante in Java). Per questo motivo, molte espressioni che coinvolgonoeq? non sono specificati, quindi se restituisce #t o #f dipende dall'implementazione.

eqv?#t per le stesse cose di eq?. È anche #t se è un numero o un carattere e il suo valore è lo stesso , anche quando i dati sono troppo grandi per essere contenuti in un puntatore. Pertanto, per quelli eqv?fa il lavoro extra di verificare che il tipo sia uno dei supportati, che entrambi siano dello stesso tipo e che gli oggetti di destinazione abbiano lo stesso valore di dati.

equal?è #t per le stesse cose di eqv?e se è un tipo composto come coppia, vettore, stringa e bytevector lo fa ricorsivamente equal?con le parti. In pratica restituirà #t se i due oggetti hanno lo stesso aspetto . Prima di R6RS, non è sicuro da usare equal?su strutture circolari.

=è simile eqv?ma funziona solo per i tipi numerici . Potrebbe essere più efficiente.

string=?è come equal?, ma funziona solo per le stringhe. Potrebbe essere più efficiente.


6

equal? confronta ricorsivamente due oggetti (di qualsiasi tipo) per verificarne l'uguaglianza.

  • Si noti che questo potrebbe essere costoso per una struttura di dati di grandi dimensioni poiché potenzialmente l'intero elenco, stringa, vettore, ecc. Deve essere attraversato.

  • Se l'oggetto contiene solo un singolo elemento (ad es .: numero, carattere, ecc.), Questo è lo stesso di eqv?.


eqv? verifica due oggetti per determinare se entrambi sono "normalmente considerati come lo stesso oggetto".

  • eqv?e eq?sono operazioni molto simili e le differenze tra loro saranno in qualche modo specifiche dell'implementazione.

eq?è uguale a eqv?ma può essere in grado di discernere distinzioni più fini e può essere implementato in modo più efficiente.

  • Secondo le specifiche, questo potrebbe essere implementato come un confronto puntatore veloce ed efficiente, al contrario di un'operazione più complicata per eqv?.


= confronta i numeri per l'uguaglianza numerica.

  • Si noti che possono essere forniti più di due numeri, ad esempio: (= 1 1.0 1/1 2/2)

Pensavo eq?fosse l'uguaglianza effettiva del puntatore (non eqv?). È "il migliore o il più esigente". Ad esempio, (eqv? 2 2)è garantito #t, ma (eq? 2 2)è "non specificato". Cioè dipende dal fatto che un'implementazione crei un nuovo oggetto di memoria effettivo per ogni nuovo numero letto o riutilizzi uno creato in precedenza se possibile.
Will Ness

@ WillNess - Buona cattura, grazie. Le differenze tra eq?e eqv?sono più sottili delle altre operazioni.
Justin Ethier

5

Non si menziona l'implementazione di uno schema, ma in Racket eq?restituisce true solo se gli argomenti si riferiscono allo stesso oggetto. Il secondo esempio restituisce #f perché il sistema sta creando un nuovo numero in virgola mobile per ogni argomento; non sono lo stesso oggetto.

equal?e =stanno verificando l'equivalenza del valore, ma =è applicabile solo ai numeri.

Se stai usando Racket, controlla qui per maggiori informazioni. Altrimenti, controlla la documentazione dell'implementazione del tuo schema.


3
Meglio ancora ... Leggi le specifiche ... r6rs.org/final/html/r6rs/r6rs-ZH-14.html#node_sec_11.5
Dirk

3

Pensa eq?all'uguaglianza dei puntatori. Gli autori del Rapporto vogliono che sia il più generale possibile, quindi non lo dicono apertamente perché dipende dall'implementazione e, per dirlo, favorirebbero le implementazioni basate su puntatori. Ma dicono

Di solito sarà possibile implementare l'eq? molto più efficiente di eqv ?, ad esempio, come semplice confronto con il puntatore

Ecco cosa intendo. (eqv? 2 2)è garantito il ritorno #tma (eq? 2 2)non è specificato. Ora immagina un'implementazione basata su puntatori. In esso eq?è solo un confronto puntatore. Da(eq? 2 2) non è specificato, significa che questa implementazione è libera di creare semplicemente una nuova rappresentazione dell'oggetto di memoria di ogni nuovo numero che legge dal codice sorgente. eqv?deve effettivamente esaminare i suoi argomenti.

OTOH (eq 'a 'a)è #t. Ciò significa che tale attuazione deve riconoscere simboli con nomi duplicati e utilizzare lo stesso uno oggetto di rappresentazione in memoria per tutti loro.

Supponiamo che un'implementazione non sia basata su puntatori. Finché aderisce al Rapporto, non importa. Gli autori semplicemente non vogliono essere visti mentre dettano le specifiche delle implementazioni agli implementatori, quindi scelgono attentamente la loro formulazione.

Questa è la mia ipotesi comunque.

Quindi, molto grossolanamente, eq?è l'uguaglianza del puntatore, eqv?è (atomico) sensibile ai valori, equal?è anche sensibile alla struttura (controlla i suoi argomenti in modo ricorsivo, in modo che alla fine (equal? '(a) '(a))sia necessario #t), =è per i numeri, string=?è per le stringhe, ei dettagli sono nella relazione.


0

Oltre alle risposte precedenti, aggiungerò alcuni commenti.

Tutti questi predicati vogliono definire la funzione astratta di identityper un oggetto ma in contesti differenti.

EQ?è dipendente dall'implementazione e non risponde alla domanda are 2 objects the same?solo in caso di utilizzo limitato. Dal punto di vista dell'implementazione, questo predicato confronta solo 2 numeri (puntatore a oggetti), non guarda il contenuto degli oggetti. Quindi, ad esempio, se la tua implementazione non mantiene in modo univoco le stringhe all'interno ma alloca una memoria diversa per ogni stringa, allora (eq? "a" "a")sarà falso.

EQV?- questo guarda all'interno degli oggetti, ma con un uso limitato. È dipendente dall'implementazione se restituisce true per (eqv? (lambda(x) x) (lambda(x) x)). Ecco una filosofia completa su come definire questo predicato, poiché al giorno d'oggi sappiamo che esistono alcuni metodi veloci per confrontare la funzionalità di alcune funzioni, con un uso limitato. Ma eqv?fornisce una risposta coerente per grandi numeri, stringhe, ecc.

In pratica, alcuni di questi predicati cercano di utilizzare la definizione astratta di un oggetto (matematicamente), mentre altri utilizzano la rappresentazione di un oggetto (come è implementato su una macchina reale). La definizione matematica di identità viene da Leibniz e dice:

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.

L'ideale sarebbe poter implementare questa stessa definizione su computer ma per ragioni di indecidibilità e / o velocità non è implementata letteralmente. Questo è il motivo per cui ci sono molti operatori che cercano ciascuno di concentrarsi su diversi punti di vista attorno a questa definizione.

Prova a immaginare la definizione astratta di un'identità per una continuazione. Anche se è possibile fornire una definizione di un sottoinsieme di funzioni ( classe di funzioni ricorsive sigma ), il linguaggio non impone che alcun predicato sia vero o falso. Complicherebbe molto sia la definizione del linguaggio che molto di più l'implementazione.

Il contesto per gli altri predicati è più facile da analizzare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.