8

在我的大学里,我们不得不和 Racket 一起工作,因为我有点喜欢它,所以我从 No Starch 那里买了最近出版的书“Realm Of Racket”。

到目前为止很好,但是,当他们试图解释eq? 作品:

  1. 起初,他们解释如何平等?比较两个值是否由相同的部分组成。好的,没问题,我明白了:相等?与 Java 的equals(someObject)方法几乎相同。如果两个对象/结构/任何内容相同,则返回#t。
  2. 然后,我想,eq?必须等同于 Java 的==运算符,它不比较内容,而是基于引用。
  3. 这个想法似乎被书中下面的一句话印证了:“eq?比较改变一个结构是否会改变另一个结构......”太好了!让我们将其与以下 Java 代码进行比较:

    Point p1 = new Point(5, 5);
    Point p2 = p1;
    System.out.println(p1 == p2);   // true, since the reference has been copied.
    System.out.println(p1.x);       // 5
    System.out.println(p2.x);       // 5
    p1.x = 42;
    System.out.println(p1.x);       // 42
    System.out.println(p2.x);       // Accordingly, 42
    

    让我们在 Racket 中试试这个:

    (define cons1 (cons 1 empty))
    (define cons2 cons1)
    (eq? cons1 cons2)           ;; #t, since the refernce has been copied.
    (set! cons1 (cons 2 empty))
    cons1                       ;; Returns '(2) - as expected.
    cons2                       ;; Still returns '(1).
    

    为什么?cons2 指向 cons1,它本身指向 '(2)。另外,他们不是说一换一换就平等吗?

显然,现在我不明白为什么这不符合预期,因此,我看不到什么eq?是在做。也许我错了,它与参考没有任何关系......

如果有人知道这一点,请分享您的智慧;)

4

3 回答 3

8

有关如何eq?工作的技术说明,请查看当前规范,您将找不到更详细的参考。或者简单地查看 Racket 关于该主题的文档,特别是eq?程序eqv?equal?. 关于您的问题-Scheme 代码中的结果符合预期且正确,让我们看看原因。请注意,在 Java 的这一行中:

p1.x = 42;

您正在修改 和 指向的同一个对象。而在这一行:p1p2

(set! cons1 (cons 2 empty))

您正在创建一个的、不同的对象并设置cons1指向它,但cons2仍指向旧对象。您可以确认这一点,在上一行之后,比较(eq? cons1 cons2)将返回#f

关键是:这些例子是不等价的。Java 示例处理由两个不同引用指向的单个对象,而 Scheme 示例处理两个对象和两个引用。

出于比较的目的,这里有一个类似于 Java 代码的 Scheme 示例,并且可以按您预期的方式工作,因为在这里我们正在修改一个由两个引用指向的可变对象:

#lang racket
(require scheme/mpair) ;; `m` stands for "mutable"

(define p1 (mlist 5 5))
(define p2 p1)

(eq? p1 p2)       ;; #t
(mcar p1)         ;;  5
(mcar p2)         ;;  5

(set-mcar! p1 42)
(eq? p1 p2)       ;; #t
(mcar p1)         ;; 42
(mcar p2)         ;; 42
于 2013-07-10T13:50:57.863 回答
4

啊:你要确保你正在改变结构。在您的示例中,它实际上并没有改变现有值的结构,而是构建一个全新的值并指向cons1它。你有正确的概念:eq?几乎是 Java 的==.

打个比方,这里是 Java 形式的错误,只是为了让您看看出了什么问题:

int[] lst1 = new int[] { 1 };       // (define cons1 (cons 1 empty))
int[] lst2 = lst1;                  // (define cons2 cons1)
System.out.println(lst1 == lst2);   // (eq? cons1 cons2)
lst1 = new int[] { 2 };             // (set! cons1 (cons 2 empty))
System.out.println(lst1[0]);        // (list-ref cons1 0)
System.out.println(lst2[0]);        // (list-ref cons2 0)

事实上,此时,您需要eq?在结尾处检查 -ness:您会看到两者不再是eq?:它们是两个不同的值。

您真正想要的是对结构进行突变,而不是重新绑定变量。在 Racket 中,由于列表是不可变的,因此您需要使用允许突变的不同数据结构。 向量是一种可以演示的示例数据类型。让我们再次“罗塞塔”这个类比:

(define vec1 (vector 1))          ;; int[] vec1 = new int[] { 1 };
(define vec2 vec1)                ;; int[] vec2 = vec1;
(eq? vec1 vec2)                   ;; System.out.println(vec1 == vec2);
(vector-set! vec1 0 2)            ;; vec1[0] = 2;
(vector-ref vec1 0)               ;; System.out.println(vec1[0]);
(vector-ref vec2 0)               ;; System.out.println(vec2[0]);
于 2013-07-10T16:14:53.587 回答
3

你是对的,eq?比较引用以确定相等性。equal?这比必须递归地比较对象要有效得多。

Racket文档明确定义了每个功能以及相关eqv?功能:

平等的?

两个值相等?当且仅当它们是 eqv?,除非针对特定数据类型另有规定。

进一步指定相等的数据类型?包括字符串、字节字符串、对、可变对、向量、框、哈希表和可检查的结构。在最后六种情况下,相等是递归定义的;如果 v1 和 v2 都包含参考循环,则当值的无限展开相等时,它们是相等的。

例子:

> (equal? 'yes 'yes)
#t
> (equal? 'yes 'no)
#f
> (equal? (expt 2 100) (expt 2 100))
#t
> (equal? 2 2.0)
#f
> (equal? (make-string 3 #\z) (make-string 3 #\z))
#t

当量?

两个值是 eqv? 当且仅当它们是 eq?,除非针对特定数据类型另有规定。

number 和 character 数据类型是唯一的 eqv? 与 eq 不同?

例子:

> (eqv? 'yes 'yes)
#t
> (eqv? 'yes 'no)
#f
> (eqv? (expt 2 100) (expt 2 100))
#t
> (eqv? 2 2.0)
#f
> (eqv? (integer->char 955) (integer->char 955))
#t
> (eqv? (make-string 3 #\z) (make-string 3 #\z))
#f

情商?

如果 v1 和 v2 引用同一个对象,则返回 #t,否则返回 #f。

例子:

> (eq? 'yes 'yes)
#t
> (eq? 'yes 'no)
#f
> (let ([v (mcons 1 2)]) (eq? v v))
#t
> (eq? (mcons 1 2) (mcons 1 2))
#f
> (eq? (make-string 3 #\z) (make-string 3 #\z))
#f
于 2013-07-10T13:55:02.737 回答