Qual é a diferença entre eq ?, eqv ?, equal? ​​E = in Scheme?

87

Eu me pergunto qual é a diferença entre essas operações no Scheme. Eu vi perguntas semelhantes no Stack Overflow, mas elas são sobre Lisp, e não há uma comparação entre três desses operadores.

Estou escrevendo os diferentes tipos de comandos no Scheme e obtenho as seguintes saídas:

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

Por que isso acontece?

yrazlik
fonte
3
e há também eqv?, o que significa algo diferente de eq?ouequal?
newacct

Respostas:

160

Vou responder a essa pergunta aos poucos. Vamos começar com o =predicado de equivalência. O =predicado é usado para verificar se dois números são iguais. Se você fornecer qualquer coisa além de um número, ele gerará um erro:

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

O eq?predicado é usado para verificar se seus dois parâmetros representam o mesmo objeto na memória. Por exemplo:

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

Observe, entretanto, que há apenas uma lista vazia '()na memória (na verdade, a lista vazia não existe na memória, mas um ponteiro para o local da memória 0é considerado a lista vazia). Portanto, ao comparar listas vazias eq?sempre retornará #t(porque representam o mesmo objeto na memória):

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

Agora, dependendo da implementação eq?pode ou não retornar #tpara valores primitivos, como números, strings, etc. Por exemplo:

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

É aqui que o eqv?predicado entra em cena. O eqv?é exatamente o mesmo que o eq?predicado, exceto que sempre retornará #tpara os mesmos valores primitivos. Por exemplo:

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

Portanto, eqv?é um superconjunto de eq?e, na maioria dos casos, você deve usar em eqv?vez de eq?.

Finalmente chegamos ao equal?predicado. O equal?predicado é exatamente o mesmo que o eqv?predicado, exceto que também pode ser usado para testar se duas listas, vetores, etc. têm elementos correspondentes que satisfazem o eqv?predicado. Por exemplo:

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

Em geral:

  1. Use o =predicado quando quiser testar se dois números são equivalentes.
  2. Use o eqv?predicado quando desejar testar se dois valores não numéricos são equivalentes.
  3. Use o equal?predicado quando quiser testar se duas listas, vetores, etc. são equivalentes.
  4. Não use o eq?predicado, a menos que saiba exatamente o que está fazendo.
Aadit M Shah
fonte
7
AFAIK (eqv? "a" "a") ==> unspecified. Você terá que usar equal?ou (o possivelmente mais otimizado)string=?
Sylwester
3
de acordo com o Relatório , não(eq? '(1) '(1)) é especificado , portanto sua (define x '(1 2))ilustração pode não funcionar.
Will Ness
4
Muito preciso e informativo. Especialmente as orientações no final.
Germán Diago
2
Mas eq? parece ser definido para símbolos e isso deve ser observado! Se os símbolos forem iguais, eq? retorna #t. Exemplo (eq? 'foo 'foo) -> #t, (eq? 'foo 'bar)-> false`. Eu li isso aqui e aqui
Nedko
13

Existem duas páginas completas na especificação RnRS relacionadas a eq?, eqv?, equal? and =. Aqui está a especificação preliminar do R7RS . Confira!

Explicação:

  • = compara números, 2,5 e 2,5 são numericamente iguais.
  • equal?para números reduzidos =, 2,5 e 2,5 são numericamente iguais.
  • eq?compara 'ponteiros'. O número 5, em sua implementação do Scheme, é implementado como um 'imediato' (provável), portanto, 5 e 5 são idênticos. O número 2,5 pode exigir uma alocação de um 'registro de ponto flutuante' em sua implementação de Scheme, os dois ponteiros não são idênticos.
GoZoner
fonte
1
O link para o rascunho da especificação R7RS está morto em 04/02/2018
Jeremiah Peschka
2
Atualizado para um link ativo.
GoZoner
10

eq?é #tquando é o mesmo endereço / objeto. Normalmente, pode-se esperar #t para o mesmo símbolo, booleano e objeto e #f para valores que são de tipo diferente, com valores diferentes, ou não a mesma estrutura Scheme / Lisp-implementations tem uma tradição de incorporar tipo em seus ponteiros e incorporar valores no mesmo espaço se houver espaço suficiente. Assim, alguns ponteiros realmente não são endereços, mas valores, como o char Rou o Fixnum 10. Isso acontecerá eq?porque o "endereço" é um tipo + valor incorporado. Algumas implementações também reutilizam constantes imutáveis. (eq? '(1 2 3)' (1 2 3)) pode ser #f quando interpretado, mas #t quando compilado, pois pode obter o mesmo endereço. (Como o pool String constante em Java). Por causa disso, muitas expressões envolvendoeq? não são especificados, portanto, se for avaliado como #t ou #f, depende da implementação.

eqv?são #t para as mesmas coisas que eq?. Também é #t se for um número ou caractere e seu valor for o mesmo , mesmo quando os dados são muito grandes para caber em um ponteiro. Assim, para esses eqv?, o trabalho extra de verificar se o tipo é um dos suportados, se ambos são do mesmo tipo e se os objetos de destino têm o mesmo valor de dados.

equal?é #t para as mesmas coisas que eqv?e se for um tipo composto como par, vetor, string e bytevector que ele faz recursivamente equal?com as partes. Na prática, ele retornará #t se os dois objetos forem iguais . Antes do R6RS, não era seguro usar equal?em estruturas circulares.

=é semelhante, eqv?mas só funciona para tipos numéricos . Pode ser mais eficiente.

string=?é parecido equal?, mas só funciona para strings. Pode ser mais eficiente.

Sylwester
fonte
6

equal? compara recursivamente dois objetos (de qualquer tipo) para igualdade.

  • Observe que isso pode ser caro para uma grande estrutura de dados, já que potencialmente toda a lista, string, vetor, etc. deve ser percorrida.

  • Se o objeto contém apenas um único elemento (por exemplo: número, caractere, etc), é o mesmo que eqv?.


eqv? testa dois objetos para determinar se ambos são "normalmente considerados o mesmo objeto".

  • eqv?e eq?são operações muito semelhantes, e as diferenças entre eles serão um tanto específicas da implementação.

eq?é o mesmo que, eqv?mas pode ser capaz de discernir distinções mais sutis e pode ser implementado com mais eficiência.

  • De acordo com as especificações, isso pode ser implementado como uma comparação de ponteiro rápida e eficiente, em oposição a uma operação mais complicada para eqv?.


= compara números para igualdade numérica.

  • Observe que mais de dois números podem ser fornecidos, por exemplo: (= 1 1.0 1/1 2/2)
Justin Ethier
fonte
Eu pensei que eq?era a igualdade real do ponteiro (não eqv?). É "o melhor ou o mais discriminador". Por exemplo, (eqv? 2 2)é garantido que #tsim, mas (eq? 2 2)é "não especificado". Ou seja, depende se uma implementação cria um novo objeto de memória real para cada número recém-lido ou reutiliza um previamente criado, se puder.
Will Ness
@WillNess - Boa captura, obrigado. As diferenças entre eq?e eqv?são mais sutis do que as outras operações.
Justin Ethier
5

Você não menciona uma implementação de esquema, mas em Racket, eq?só retorna verdadeiro se os argumentos se referirem ao mesmo objeto. Seu segundo exemplo está produzindo #f porque o sistema está criando um novo número de ponto flutuante para cada argumento; eles não são o mesmo objeto.

equal?e =estão verificando a equivalência de valor, mas =é aplicável apenas a números.

Se você estiver usando o Racket, verifique aqui para mais informações. Caso contrário, verifique a documentação da implementação do seu esquema.

Alan Gilbert
fonte
3
Melhor ainda ... Leia a especificação ... r6rs.org/final/html/r6rs/r6rs-ZH-14.html#node_sec_11.5
Dirk
3

Pense eq?em uma igualdade de ponteiro. Os autores do Relatório querem que seja o mais geral possível, então eles não dizem isso abertamente porque é dependente da implementação, e dizer isso favoreceria as implementações baseadas em ponteiros. Mas eles dizem

Normalmente será possível implementar eq? muito mais eficiente do que eqv ?, por exemplo, como uma comparação de ponteiro simples

Aqui está o que quero dizer. (eqv? 2 2)tem garantia de retorno, #tmas (eq? 2 2)não é especificado. Agora imagine uma implementação baseada em ponteiro. Nele eq?é apenas uma comparação de ponteiro. Como (eq? 2 2)não é especificado, significa que essa implementação está livre para apenas criar uma nova representação do objeto de memória de cada novo número que lê do código-fonte.eqv?deve realmente inspecionar seus argumentos.

OTOH (eq 'a 'a) é #t. Isso significa que essa aplicação terá de reconhecer símbolos com nomes duplicados e usam o mesmo um objeto de representação na memória de todos eles.

Suponha que uma implementação não seja baseada em ponteiros. Desde que esteja de acordo com o Relatório, não importa. Os autores simplesmente não querem ser vistos como ditando as especificações das implementações para os implementadores, então eles escolhem suas palavras com cuidado.

Este é o meu palpite de qualquer maneira.

Muito grosseiramente, eq?é a igualdade de ponteiro, eqv?é (atômica-) ciente de valores, equal?também é ciente de estrutura (verifica seus argumentos recursivamente, de modo que finalmente (equal? '(a) '(a))é necessário ser #t), =é para números, string=?é para strings e os detalhes são no Relatório.

Will Ness
fonte
0

Além das respostas anteriores, acrescentarei alguns comentários.

Todos esses predicados desejam definir a função abstrata de identitypara um objeto, mas em contextos diferentes.

EQ?é dependente da implementação e não responde à pergunta are 2 objects the same?apenas em uso limitado. Do ponto de vista da implementação, este predicado apenas compara 2 números (ponteiro para objetos), ele não olha para o conteúdo dos objetos. Portanto, por exemplo, se sua implementação não mantiver exclusivamente as strings dentro, mas alocar memória diferente para cada string, então (eq? "a" "a")será falso.

EQV?- olha dentro dos objetos, mas com uso limitado. É dependente da implementação se retornar verdadeiro para (eqv? (lambda(x) x) (lambda(x) x)). Aqui está uma filosofia completa de como definir este predicado, pois sabemos hoje que existem alguns métodos rápidos para comparar a funcionalidade de algumas funções, com uso limitado. Mas eqv?fornece uma resposta coerente para grandes números, strings, etc.

Praticamente, alguns desses predicados tentam usar a definição abstrata de um objeto (matematicamente), enquanto outros usam a representação de um objeto (como ele é implementado em uma máquina real). A definição matemática de identidade vem de Leibniz e diz:

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.

Idealmente, seria ser capaz de implementar esta definição no computador, mas por razões de indecidibilidade e / ou velocidade ela não é implementada literalmente. É por isso que existem muitos operadores que tentam, cada um, focar em diferentes pontos de vista em torno dessa definição.

Tente imaginar a definição abstrata de uma identidade para uma continuação. Mesmo que você possa fornecer uma definição de um subconjunto de funções ( classe sigma-recursiva de funções ), a linguagem não impõe que nenhum predicado seja verdadeiro ou falso. Isso complicaria muito tanto a definição da linguagem quanto muito mais a implementação.

O contexto para os outros predicados é mais fácil de analisar.

alinsoar
fonte