¿Cuál es la diferencia entre eq ?, eqv ?, ¿igual ?, y = en Scheme?

85

Me pregunto cuál es la diferencia entre esas operaciones en Scheme. He visto preguntas similares en Stack Overflow, pero son sobre Lisp y no hay una comparación entre tres de esos operadores.

Estoy escribiendo los diferentes tipos de comandos en Scheme y obtengo los siguientes resultados:

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

¿Por qué es este el caso?

yrazlik
fuente
3
y también hay eqv?, que significa algo diferente de eq?oequal?
newacct

Respuestas:

155

Responderé esta pregunta de forma incremental. Comencemos con el =predicado de equivalencia. El =predicado se usa para verificar si dos números son iguales. Si le proporciona algo más que un número, generará un error:

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

El eq?predicado se usa para verificar si sus dos parámetros representan el mismo objeto en la memoria. Por ejemplo:

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

Sin embargo, tenga en cuenta que solo hay una lista vacía '()en la memoria (en realidad, la lista vacía no existe en la memoria, pero un puntero a la ubicación de la memoria 0se considera la lista vacía). Por lo tanto, cuando se comparan listas vacías eq?, siempre se devolverá #t(porque representan el mismo objeto en la memoria):

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

Ahora, dependiendo de la implementación, eq?puede o no regresar #tpara valores primitivos como números, cadenas, etc. Por ejemplo:

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

Aquí es donde eqv?entra en escena el predicado. El eqv?es exactamente igual que el eq?predicado, excepto que siempre regresará #tpara los mismos valores primitivos. Por ejemplo:

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

Por eqv?lo tanto, es un superconjunto de eq?y, en la mayoría de los casos, debe usar en eqv?lugar de eq?.

Finalmente llegamos al equal?predicado. El equal?predicado es exactamente el mismo que el eqv?predicado, excepto que también se puede usar para probar si dos listas, vectores, etc. tienen elementos correspondientes que satisfacen el eqv?predicado. Por ejemplo:

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

En general:

  1. Utilice el =predicado cuando desee probar si dos números son equivalentes.
  2. Utilice el eqv?predicado cuando desee probar si dos valores no numéricos son equivalentes.
  3. Utilice el equal?predicado cuando desee probar si dos listas, vectores, etc. son equivalentes.
  4. No use el eq?predicado a menos que sepa exactamente lo que está haciendo.
Aadit M Shah
fuente
7
AFAIK (eqv? "a" "a") ==> unspecified. Tendrá que usar equal?o (posiblemente más optimizado)string=?
Sylwester
3
según el Informe , no(eq? '(1) '(1)) está especificado , por lo que es posible que su (define x '(1 2))ilustración no funcione.
Will Ness
4
Muy precisa e informativa. Especialmente las pautas al final.
Germán Diago
2
¿Pero eq? parece estar definido para símbolos y esto debe tenerse en cuenta. Si los símbolos tienen el mismo aspecto, ¿eq? devuelve #t. Ejemplo (eq? 'foo 'foo) -> #t, (eq? 'foo 'bar)-> falso`. Leí esto aquí y aquí
Nedko
13

Hay dos páginas completas en la especificación RnRS relacionadas con eq?, eqv?, equal? and =. Aquí está el borrador de la especificación R7RS . ¡Echale un vistazo!

Explicación:

  • = compara números, 2.5 y 2.5 son numéricamente iguales.
  • equal?porque los números se reducen a =, 2.5 y 2.5 son numéricamente iguales.
  • eq?compara 'punteros'. El número 5, en la implementación de su esquema, se implementa como un 'inmediato' (probable), por lo tanto, 5 y 5 son idénticos. El número 2.5 puede requerir una asignación de un 'registro de punto flotante' en su implementación de Scheme, los dos punteros no son idénticos.
GoZoner
fuente
1
El enlace al borrador de la especificación R7RS está muerto a partir del 2018-02-04
Jeremiah Peschka
2
Actualizado a un enlace en vivo.
GoZoner
10

eq?es #tcuando es la misma dirección / objeto. Normalmente, uno podría esperar #t para el mismo símbolo, booleano y objeto y #f para valores que son de tipo diferente, con valores diferentes, o que no tienen la misma estructura Las implementaciones de Scheme / Lisp tienen una tradición de incrustar el tipo en sus punteros y de incrustar valores en el mismo espacio si hay suficiente espacio. Por lo tanto, algunos punteros en realidad no son direcciones sino valores, como char Ro Fixnum 10. Esto será eq?debido a que la "dirección" es un tipo + valor incrustado. Algunas implementaciones también reutilizan constantes inmutables. (eq? '(1 2 3)' (1 2 3)) podría ser #f cuando se interpreta pero #t cuando se compila ya que podría obtener la misma dirección. (Como el grupo de cadenas constante en Java). Debido a esto, muchas expresiones que involucraneq? no están especificados, por lo tanto, si se evalúa como #t o #f depende de la implementación.

eqv?son #t por lo mismo que eq?. También es #t si es un número o carácter y su valor es el mismo , incluso cuando los datos son demasiado grandes para caber en un puntero. Por lo tanto, para aquellos eqv?, el trabajo adicional de verificar que el tipo sea uno de los admitidos, que ambos sean del mismo tipo y que los objetos de destino tengan el mismo valor de datos.

equal?es #t para las mismas cosas que eqv?y si es un tipo compuesto como par, vector, cadena y bytevector, lo hace de forma recursiva equal?con las partes. En la práctica, devolverá #t si los dos objetos se ven iguales . Antes de R6RS, no es seguro usarlo equal?en estructuras circulares.

=es como eqv?pero solo funciona para tipos numéricos . Podría ser más eficiente.

string=?es como equal?, pero solo funciona para cadenas. Podría ser más eficiente.

Sylwester
fuente
6

equal? compara recursivamente dos objetos (de cualquier tipo) para la igualdad.

  • Tenga en cuenta que esto podría resultar caro para una estructura de datos grande, ya que potencialmente se debe atravesar toda la lista, cadena, vector, etc.

  • Si el objeto solo contiene un solo elemento (EG: número, carácter, etc.), este es el mismo que eqv?.


eqv? prueba dos objetos para determinar si ambos "normalmente se consideran el mismo objeto".

  • eqv?y eq?son operaciones muy similares, y las diferencias entre ellas van a ser algo específicas de implementación.

eq?es igual que, eqv?pero puede discernir distinciones más finas y puede implementarse de manera más eficiente.

  • De acuerdo con la especificación, esto podría implementarse como una comparación de puntero rápida y eficiente, en contraposición a una operación más complicada para eqv?.


= compara números para la igualdad numérica.

  • Tenga en cuenta que se pueden proporcionar más de dos números, por ejemplo: (= 1 1.0 1/1 2/2)
Justin Ethier
fuente
Pensé que eq?era la igualdad real del puntero (no eqv?). Es "el más fino o el más exigente". Por ejemplo, (eqv? 2 2)se garantiza que lo sea #t, pero no (eq? 2 2)está "especificado". Es decir, depende de si una implementación crea un nuevo objeto de memoria real para cada número recién leído o si puede reutilizar uno creado previamente.
Will Ness
@WillNess - Buen partido, gracias. Las diferencias entre eq?y eqv?son más sutiles que las otras operaciones.
Justin Ethier
5

No mencionas la implementación de un esquema, pero en Racket, eq?solo devuelve verdadero si los argumentos se refieren al mismo objeto. Su segundo ejemplo produce #f porque el sistema está creando un nuevo número de punto flotante para cada argumento; no son el mismo objeto.

equal?y =están verificando la equivalencia de valores, pero =solo es aplicable a números.

Si está utilizando Racket, consulte aquí para obtener más información. De lo contrario, consulte la documentación de la implementación de su esquema.

Alan Gilbert
fuente
3
Mejor aún ... Lea la especificación ... r6rs.org/final/html/r6rs/r6rs-ZH-14.html#node_sec_11.5
Dirk
3

Piense eq?en la igualdad de punteros. Los autores del Informe quieren que sea lo más general posible, por lo que no dicen esto directamente porque depende de la implementación, y decirlo favorecería las implementaciones basadas en punteros. Pero ellos dicen

Normalmente será posible implementar eq? mucho más eficiente que eqv ?, por ejemplo, como una simple comparación de punteros

Esto es lo que quiero decir. (eqv? 2 2)está garantizado para regresar #tpero (eq? 2 2)no está especificado. Ahora imagina una implementación basada en punteros. En él eq?es solo una comparación de punteros. Dado (eq? 2 2)que no está especificado, significa que esta implementación es gratuita para crear una nueva representación de objeto de memoria de cada nuevo número que lee del código fuente. eqv?realmente debe inspeccionar sus argumentos.

OTOH lo (eq 'a 'a)es #t. Esto significa que dicha aplicación debe reconocer símbolos con nombres duplicados y utilizar el mismo un objeto de representación en la memoria de todos ellos.

Suponga que una implementación no está basada en punteros. Mientras se adhiera al Informe, no importa. Los autores simplemente no quieren que se les vea dictando los detalles de las implementaciones a los implementadores, por lo que eligen su redacción con cuidado.

Esta es mi suposición de todos modos.

Entonces, de manera muy aproximada, ¿ eq?es la igualdad de punteros, eqv?es (atómico-) consciente de los valores, equal?también es consciente de la estructura (verifica sus argumentos de forma recursiva, por lo que finalmente (equal? '(a) '(a))se requiere que sea #t), =es para números, string=?es para cadenas, y los detalles son en el Informe.

Will Ness
fuente
0

Aparte de las respuestas anteriores, agregaré algunos comentarios.

Todos estos predicados quieren definir la función abstracta de identitypara un objeto, pero en diferentes contextos.

EQ?depende de la implementación y no responde a la pregunta are 2 objects the same?solo con un uso limitado. Desde el punto de vista de la implementación, este predicado solo compara 2 números (puntero a objetos), no mira el contenido de los objetos. Entonces, por ejemplo, si su implementación no mantiene las cadenas dentro de forma única, sino que asigna memoria diferente para cada cadena, entonces (eq? "a" "a")será falso.

EQV?- esto mira dentro de los objetos, pero con uso limitado. Depende de la implementación si devuelve verdadero para (eqv? (lambda(x) x) (lambda(x) x)). Aquí es una filosofía completa cómo definir este predicado, ya que sabemos hoy en día que existen algunos métodos rápidos para comparar la funcionalidad de algunas funciones, con uso limitado. Pero eqv?proporciona una respuesta coherente para números grandes, cadenas, etc.

Prácticamente, algunos de estos predicados intentan usar la definición abstracta de un objeto (matemáticamente), mientras que otros usan la representación de un objeto (cómo se implementa en una máquina real). La definición matemática de identidad proviene de Leibniz y 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.

Lo ideal sería poder implementar esta misma definición en la computadora, pero por razones de indecidibilidad y / o velocidad no se implementa literalmente. Es por eso que hay muchos operadores que intentan que cada uno se enfoque en diferentes puntos de vista en torno a esta definición.

Intente imaginar la definición abstracta de una identidad para una continuación. Incluso si puede proporcionar una definición de un subconjunto de funciones ( clase de funciones sigma-recursiva ), el lenguaje no impone ningún predicado para que sea verdadero o falso. Complicaría mucho tanto la definición del lenguaje como mucho más la implementación.

El contexto de los otros predicados es más fácil de analizar.

alinsoar
fuente