Яка різниця між eq ?, eqv ?, дорівнює ?, і = у схемі?


85

Цікаво, яка різниця між цими операціями в схемі. Я бачив подібні запитання у Stack Overflow, але вони стосуються Lisp, і немає порівняння між трьома з цих операторів.

Я пишу різні типи команд у схемі, і я отримую такі результати:

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

Чому це так?


3
і є також eqv?, що означає щось відмінне від eq?абоequal?
newacct

Відповіді:


155

Я відповім на це питання поступово. Почнемо з =предиката еквівалентності. =Предикат використовується для перевірки , є чи два числа рівні. Якщо ви надасте щось інше, крім числа, це призведе до помилки:

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

eq?Предикат використовуються для перевірки два його клієнтів відображають параметрів , один і той же об'єкт в пам'яті. Наприклад:

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

Однак зверніть увагу, що '()в пам'яті є лише один порожній список (насправді порожній список не існує в пам'яті, але вказівник на місце в пам'яті 0вважається порожнім списком). Отже, при порівнянні порожніх списків eq?завжди буде повертатися #t(оскільки вони представляють один і той же об'єкт у пам'яті):

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

Тепер, залежно від реалізації, eq?може повернутися або не повернутися #tдо примітивних значень, таких як числа, рядки тощо. Наприклад:

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

Тут eqv?присудок з’являється у картині. Це eqv?точно те саме, що і eq?предикат, за винятком того, що воно завжди повертається #tдля тих самих примітивних значень. Наприклад:

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

Отже eqv?, це надмножина, eq?і для більшості випадків вам слід використовувати eqv?замість eq?.

Нарешті ми підійшли до equal?присудка. equal?Предикат точно так же , як і eqv?предикат, за винятком того, що вона також може бути використана для перевірки два списків, вектори і т.д. мають відповідні елементи , які задовольняють eqv?предикат. Наприклад:

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

Загалом:

  1. Використовуйте =присудок, коли хочете перевірити, чи еквівалентні два числа.
  2. Використовуйте eqv?предикат, коли хочете перевірити, чи еквівалентні два нечислові значення.
  3. Використовуйте equal?предикат, коли хочете перевірити, чи еквівалентні два списки, вектори тощо.
  4. Не використовуйте eq?присудок, якщо ви точно не знаєте, що робите.

7
AFAIK (eqv? "a" "a") ==> unspecified. Вам доведеться скористатися equal?або (можливо, більш оптимізованим)string=?
Sylwester

3
згідно зі звітом , (eq? '(1) '(1))є невизначеним , так що ваш (define x '(1 2))малюнок може не працювати.
Will Ness

4
Дуже точний та інформативний. Особливо настанови в кінці.
Герман Діаго

2
Але ек? здається, це визначено для символів, і це слід зазначити! Якщо символи виглядають однаково, екв? повертає #t. Приклад (eq? 'foo 'foo) -> #t, (eq? 'foo 'bar)-> false`. Я читав це тут і тут
Недко

13

У специфікації RnRS є цілі дві сторінки, пов’язані з eq?, eqv?, equal? and =. Ось проект специфікації R7RS . Перевір!

Пояснення:

  • = порівнює числа, 2,5 і 2,5 чисельно рівні.
  • equal?для чисел, зменшених до =, 2,5 і 2,5 числово рівні.
  • eq?порівнює "покажчики". Число 5 у вашій реалізації Схеми реалізовано як "негайне" (ймовірно), отже, 5 і 5 ідентичні. Число 2.5 може вимагати виділення "запису з плаваючою комою" у вашій реалізації схеми, два вказівники не ідентичні.

1
Посилання на проект Специфікації R7RS мертве станом на 04.02.2018
Єремія Пешка

2
Оновлено до активного посилання.
GoZoner

10

eq?це #tколи це однакова адреса / об’єкт. Зазвичай можна було б очікувати #t для того ж символу, логічного і об'єкта і #F для значень , який різного типу з різними значеннями, або не тієї ж самою структурою Scheme / Lisp-реалізацій має традицію для вбудовування типу в їх покажчиках і для вбудовування значення в одному просторі, якщо місця достатньо. Таким чином, деякі покажчики насправді є не адресами, а значеннями, як char Rабо Fixnum 10. Вони будуть, eq?оскільки "адреса" - це вбудований тип + значення. Деякі реалізації також повторно використовують незмінні константи. (eq? '(1 2 3)' (1 2 3)) може бути #f при інтерпретації, але #t при компіляції, оскільки може отримати ту саму адресу. (Як постійний пул рядків у Java). Через це багато висловів за участюeq? не вказані, отже, якщо вона оцінює значення #t або #f, це залежить від реалізації.

eqv?#t для тих самих речей, що і eq?. Це також #t, якщо це число або символ, і його значення однакове , навіть коли дані занадто великі, щоб вміститися в покажчик. Таким чином, для тих, хто eqv?виконує додаткову роботу, перевіряючи, чи є один із підтримуваних типів, що обидва є однаковими, а цільові об’єкти мають однакове значення даних.

equal?є #t для тих самих речей, що eqv?і якщо це складний тип, такий як пара, вектор, рядок та байтвектор, це рекурсивно робить equal?з частинами. На практиці він поверне #t, якщо два об'єкти виглядають однаково . До R6RS його небезпечно використовувати equal?на кругових конструкціях.

=схоже, eqv?але воно працює лише для числових типів . Це може бути ефективніше.

string=?схоже equal?, але воно працює лише для рядків. Це може бути ефективніше.


6

equal? рекурсивно порівнює два об'єкти (будь-якого типу) для рівності.

  • Зауважте, це може коштувати дорого для великої структури даних, оскільки потенційно потрібно обходити весь список, рядок, вектор тощо.

  • Якщо об'єкт просто містить один елемент (наприклад, число, символ тощо), це те саме, що і eqv?.


eqv? тестує два об'єкти, щоб визначити, чи обидва вони "зазвичай розглядаються як один і той же об'єкт".

  • eqv?і eq?є дуже подібними операціями, і відмінності між ними будуть дещо специфічними для реалізації.

eq?це те саме, що, eqv?однак, може розпізнавати точніші відмінності і може бути реалізовано більш ефективно.

  • Відповідно до специфікації, це може бути реалізовано як швидке та ефективне порівняння покажчиків, на відміну від більш складної операції для eqv?.


= порівнює числа для числової рівності.

  • Зверніть увагу, що можна вказати більше двох номерів, наприклад: (= 1 1.0 1/1 2/2)

Я думав, що eq?це фактична рівність покажчика (не eqv?). Це "найкраще або найвибагливіше". Напр. (eqv? 2 2)Гарантовано #t, але (eq? 2 2)є "невстановленим". Тобто це залежить від того, чи реалізація створює фактично новий об’єкт пам’яті для кожного щойно прочитаного номера, чи повторно використовує раніше створений, якщо може.
Will Ness

@WillNess - Хороший улов, дякую. Відмінності між eq?та eqv?є більш тонкими, ніж інші операції.
Джастін Етьє

5

Ви не згадуєте реалізацію схеми, але в Racket eq?повертає true , лише якщо аргументи посилаються на той самий об'єкт. Ваш другий приклад дає #f, оскільки система створює нове число з плаваючою комою для кожного аргументу; вони не однакові об'єкти.

equal?і =перевіряють еквівалентність значення, але =застосовується лише до чисел.

Якщо ви використовуєте Racket, перегляньте тут для отримання додаткової інформації. В іншому випадку перевірте документацію щодо реалізації вашої схеми.


3
А ще краще ... Прочитайте специфікацію ... r6rs.org/final/html/r6rs/r6rs-ZH-14.html#node_sec_11.5
Дірк

3

Подумайте про eq?рівність покажчика. Автори звіту хочуть, щоб він був якомога загальнішим, тому вони не говорять цього прямо, оскільки це залежить від реалізації, і, кажучи про це, було б сприятливо для реалізацій на основі покажчиків. Але вони кажуть

Зазвичай можна реалізувати еквалайзер? набагато ефективніше, ніж eqv?, наприклад, як просте порівняння покажчиків

Ось що я маю на увазі. (eqv? 2 2)гарантовано повернеться, #tале не (eq? 2 2)вказано. А тепер уявіть реалізацію на основі вказівника. У ній eq?просто порівняння покажчиків. Оскільки не (eq? 2 2)вказано, це означає, що ця реалізація може просто створювати нове представлення об'єкта пам'яті кожного нового числа, яке він зчитує з вихідного коду. eqv?повинен фактично перевірити його аргументи.

OTOH (eq 'a 'a)є #t. Це означає, що така реалізація повинна розпізнавати символи з повторюваними іменами і використовувати один і той же об'єкт представлення в пам'яті для всіх них.

Припустимо, реалізація не базується на покажчиках. Поки воно дотримується Звіту, це не має значення. Автори просто не хочуть, щоб на них диктували особливості реалізацій реалізаторам, тому вони ретельно вибирають їх формулювання.

Це все одно моя здогадка.

Отже, дуже грубо, eq?це рівність покажчика, eqv?знає (атомні) значення, equal?також знає структуру (перевіряє свої аргументи рекурсивно, так що нарешті (equal? '(a) '(a))потрібно бути #t), =це для чисел, string=?для рядків, а деталі є у звіті.


0

Окрім попередніх відповідей, я додаю кілька коментарів.

Всі ці предикати хочуть визначити абстрактну функцію identityдля об'єкта, але в різному контексті.

EQ?залежить від реалізації і не відповідає на питання are 2 objects the same?лише в обмеженому використанні. З точки зору реалізації, цей предикат просто порівнює 2 числа (вказівник на об'єкти), він не розглядає зміст об'єктів. Так, наприклад, якщо ваша реалізація не однозначно зберігає рядки всередині, а виділяє різну пам’ять для кожного рядка, тоді (eq? "a" "a")буде помилковим.

EQV?- це виглядає всередині об’єктів, але з обмеженим використанням. Це залежить від реалізації, якщо повертає true для (eqv? (lambda(x) x) (lambda(x) x)). Тут є повна філософія, як визначити цей предикат, оскільки сьогодні ми знаємо, що існує кілька швидких методів порівняння функціональності деяких функцій з обмеженим використанням. Але eqv?забезпечує послідовну відповідь на великі числа, рядки тощо.

Практично, деякі з цих предикатів намагаються використовувати абстрактне визначення об'єкта (математично), тоді як інші використовують представлення об'єкта (як це реалізовано на реальній машині). Математичне визначення тотожності походить від Лейбніца, і воно говорить:

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.

В ідеалі було б мати можливість реалізувати саме це визначення на комп'ютері, але з міркувань невизначеності та / або швидкості воно не застосовується буквально. Ось чому існує багато операторів, які намагаються кожен з них зосередитись на різних точках зору навколо цього визначення.

Спробуйте уявити абстрактне визначення особистості для продовження. Навіть якщо ви можете надати визначення підмножини функцій ( сигма-рекурсивний клас функцій ), мова не нав'язує жодного предиката як істинний чи хибний. Це значно ускладнило б і визначення мови, і набагато більше її реалізацію.

Контекст для інших предикатів легше проаналізувати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.