web-dev-qa-db-fra.com

Quelle est la différence entre eq ?, eqv ?, equal ?, et = in Scheme?

Je me demande quelle est la différence entre ces opérations. J'ai vu des questions similaires dans Stack Overflow mais elles concernent LISP, et il n'y a pas de comparaison entre trois de ces opérateurs. Donc, si cela a déjà été demandé, faites-le moi savoir.

J'écris les différents types de commandes dans Scheme et j'obtiens les sorties suivantes:

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

Quelqu'un peut-il expliquer pourquoi c'est le cas?

71
yrazlik

Je vais répondre à cette question progressivement. Commençons par le prédicat d'équivalence =. Le prédicat = Est utilisé pour vérifier si deux nombres sont égaux. Si vous lui fournissez autre chose qu'un numéro, cela générera une erreur:

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

Le prédicat eq? Est utilisé pour vérifier si ses deux paramètres représentent le même objet en mémoire. Par exemple:

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

Notez cependant qu'il n'y a qu'une seule liste vide '() en mémoire (en fait la liste vide n'existe pas en mémoire, mais un pointeur vers l'emplacement mémoire 0 Est considéré comme la liste vide). Par conséquent, lors de la comparaison de listes vides, eq? Renvoie toujours #t (Car elles représentent le même objet en mémoire):

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

En fonction de l'implémentation, eq? Peut renvoyer ou non #t Pour des valeurs primitives telles que des nombres, des chaînes, etc. Par exemple:

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

C'est là que le prédicat eqv? Entre en scène. Le eqv? Est exactement le même que le prédicat eq?, Sauf qu'il renverra toujours #t Pour les mêmes valeurs primitives. Par exemple:

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

Par conséquent, eqv? Est un surensemble de eq? Et dans la plupart des cas, vous devez utiliser eqv? Au lieu de eq?.

Enfin, nous arrivons au prédicat equal?. Le prédicat equal? Est exactement le même que le prédicat eqv?, Sauf qu'il peut également être utilisé pour tester si deux listes, vecteurs, etc. ont des éléments correspondants qui satisfont le eqv? prédicat. Par exemple:

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

En général:

  1. Utilisez le prédicat = Lorsque vous souhaitez tester si deux nombres sont équivalents.
  2. Utilisez le prédicat eqv? Lorsque vous souhaitez tester si deux valeurs non numériques sont équivalentes.
  3. Utilisez le prédicat equal? Lorsque vous souhaitez tester si deux listes, vecteurs, etc. sont équivalents.
  4. N'utilisez pas le prédicat eq? Sauf si vous savez exactement ce que vous faites.
132
Aadit M Shah

Il existe deux pages complètes dans la spécification RNRS relatives à eq?, eqv?, equal? and =. Voici le Projet de spécification R7RS . Vérifiez-le!

Explication:

  • = compare les nombres, 2,5 et 2,5 sont numériquement égaux.
  • equal? pour les nombres réduit à =, 2,5 et 2,5 sont numériquement égaux.
  • eq? compare les "pointeurs". Le numéro 5, dans votre implémentation de Scheme, est implémenté comme "immédiat" (probable), donc 5 et 5 sont identiques. Le nombre 2.5 peut nécessiter une allocation d'un "enregistrement à virgule flottante" dans votre implémentation Scheme, les deux pointeurs ne sont pas identiques.
13
GoZoner

eq? est #t lorsqu'il s'agit de la même adresse/du même objet. Normalement, on pourrait s'attendre à #t pour le même symbole, booléen et objet et #f pour des valeurs de type différent, avec des valeurs différentes ou pas la même structure Les implémentations de Scheme/LISP ont une tradition à intégrer saisissez leurs pointeurs et incorporez des valeurs dans le même espace si l'espace est suffisant. Ainsi, certains pointeurs ne sont vraiment pas des adresses mais des valeurs, comme le char R ou le Fixnum 10. Ce seront eq? puisque "l'adresse" est un type + une valeur incorporée. Certaines implémentations réutilisent également des constantes immuables. (eq? '(1 2 3)' (1 2 3)) peut être #f lorsqu'il est interprété mais #t lorsqu'il est compilé car il peut obtenir la même adresse. (Comme le pool de chaînes constant en Java). Pour cette raison, de nombreuses expressions impliquant eq? ne sont pas spécifiés, donc si elle est évaluée à #t ou #f dépend de l'implémentation.

eqv? sont #t pour les mêmes choses que eq?. Il s'agit également de #t s'il s'agit d'un nombre ou d'un caractère et que sa valeur est la même, même lorsque les données sont trop grandes pour tenir dans un pointeur. Ainsi, pour ceux eqv? fait le travail supplémentaire de vérifier que ce type est l'un des pris en charge, que les deux sont du même type et que ses objets cibles ont la même valeur de données.

equal? est #t pour les mêmes choses que eqv? et s'il s'agit d'un type composé comme paire, vecteur, chaîne et octet, il le fait récursivement equal? avec les pièces. En pratique, il retournera #t si les deux objets se ressemblent. Avant R6RS, il n'est pas sûr d'utiliser equal? sur les structures circulaires.

= est comme eqv? mais cela ne fonctionne que pour les types numériques. Cela pourrait être plus efficace.

string=? est comme equal?, mais cela ne fonctionne que pour les chaînes. Il pourrait être plus efficace.

8
Sylwester

Vous ne mentionnez pas une implémentation de schéma, mais dans Racket, eq? ne renvoie vrai que si les arguments font référence au même objet. Votre deuxième exemple donne #f car le système crée un nouveau nombre à virgule flottante pour chaque argument; ce n'est pas le même objet.

equal? et = vérifie l'équivalence des valeurs, mais = ne s'applique qu'aux nombres.

Si vous utilisez Racket, vérifiez ici pour plus d'informations. Sinon, consultez la documentation de votre implémentation de schéma.

5
Alan Gilbert

Penser à eq? comme égalité de pointeur. Les auteurs du Report veulent que ce soit aussi général que possible, donc ils ne disent pas cela carrément parce que cela dépend de l'implémentation, et pour le dire, ils préféreraient les implémentations basées sur un pointeur. Mais ils disent

Il sera généralement possible d'implémenter eq? beaucoup plus efficacement que eqv?, par exemple, comme une simple comparaison de pointeur

Voici ce que je veux dire. (eqv? 2 2) est garanti pour renvoyer #t mais (eq? 2 2) n'est pas spécifié. Imaginez maintenant une implémentation basée sur un pointeur. En elle eq? n'est qu'une comparaison de pointeurs. Puisque (eq? 2 2) n'est pas spécifié, cela signifie que cette implémentation est libre de simplement créer une nouvelle représentation d'objet mémoire de chaque nouveau nombre lu à partir du code source. eqv? doit effectivement inspecter ses arguments.

OTOH (eq 'a 'a) est #t. Cela signifie qu'une telle implémentation doit reconnaître les symboles avec des noms en double et utiliser le même un objet de représentation en mémoire pour chacun d'eux.

Supposons qu'une implémentation ne soit pas basée sur un pointeur. Tant qu'il adhère au rapport, cela n'a pas d'importance. Les auteurs ne veulent tout simplement pas être perçus comme dictant les détails des implémentations aux implémenteurs, ils choisissent donc soigneusement leur libellé.

C'est ma supposition de toute façon.

Donc très grossièrement, eq? est l'égalité du pointeur, eqv? est sensible aux valeurs (atomiques), equal? est également sensible à la structure (vérifie récursivement ses arguments, de sorte que finalement (equal? '(a) '(a)) doit être #t), = est pour les nombres, string=? est pour les chaînes, et les détails sont dans le rapport.

3
Will Ness