В чем разница между eq ?, eqv ?, equal? ​​И = в схеме?

87

Интересно, в чем разница между этими операциями в схеме. Я видел похожие вопросы в 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

Ответы:

160

Я отвечу на этот вопрос постепенно. Начнем с =предиката эквивалентности. =Предикат используется для проверки , являются ли два числа равны. Если вы укажете что-либо еще, кроме числа, это вызовет ошибку:

(= 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
Очень точно и информативно. Особенно рекомендации в конце.
Germán Diago
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 может потребовать выделения «записи с плавающей запятой» в вашей реализации схемы, два указателя не идентичны.
GoZoner
источник
1
Ссылка на проект спецификации R7RS не работает с 04.02.2018
Джеремия Пешка
2
Обновился до живой ссылки.
GoZoner 05
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?более тонкие, чем другие операции.
Justin Ethier
5

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

equal?и =проверяют эквивалентность значений, но =применимо только к числам.

Если вы используете Racket, проверьте здесь дополнительную информацию. В противном случае проверьте документацию по реализации вашей схемы.

Алан Гилберт
источник
3
А еще лучше ... Прочтите спецификацию ... r6rs.org/final/html/r6rs/r6rs-ZH-14.html#node_sec_11.5
Дирк
3

Думайте об этом eq?как о равенстве указателей. Авторы отчета хотят, чтобы он был как можно более общим, поэтому они не говорят об этом прямо, потому что это зависит от реализации, и, чтобы сказать, что это будет, предпочтительнее реализации на основе указателей. Но они говорят

Обычно можно будет реализовать 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")будет false.

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.

В идеале это определение можно было бы реализовать на компьютере, но по причинам неопределенности и / или скорости оно не реализовано буквально. Вот почему существует множество операторов, которые пытаются сосредоточить внимание каждого на разных точках зрения на это определение.

Попробуйте представить себе абстрактное определение идентичности для продолжения. Даже если вы можете предоставить определение подмножества функций ( сигма-рекурсивный класс функций ), язык не требует, чтобы какой-либо предикат был истинным или ложным. Это сильно усложнило бы как определение языка, так и реализацию.

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

Alinsoar
источник