【问题标题】:How does eq? in Racket work?eq 怎么样?在球拍工作?
【发布时间】:2013-07-08 11:22:11
【问题描述】:

在我的大学里,我们不得不与 Racket 合作,因为我有点喜欢它,所以我从 No Starch 购买了最近出版的书“Racket 领域”。

到目前为止很好,但是,当他们试图解释 eq? 的工作原理时,我无法弄清楚它们在第 4 章中的含义:

  1. 首先,他们解释了 equal? 如何比较两个值是否由相同的部分组成。好的,没问题,我明白了: equal? 与 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? 在做什么。也许我错了,它与引用没有任何关系......

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

【问题讨论】:

    标签: lisp scheme racket


    【解决方案1】:

    有关eq? 工作原理的技术说明,请查看当前的specification,您将找不到更详细的参考资料。或者简单地查看 Racket 的documentation 主题,特别是程序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
    

    【讨论】:

      【解决方案2】:

      啊:你想确保你正在改变结构。在您的示例中,它实际上并没有改变现有值的结构,而是构造一个全新的值并将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?:它们是两个不同的值。

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

      (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]);
      

      【讨论】:

        【解决方案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?

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

        数字和字符数据类型是唯一的 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
        

        【讨论】:

        • eq? 部分必须保证为 eq?。现实中还有更多 eq?,但未来可能无法保证,例如:(eq? (+ 4 5) (+ 5 4)) ==> #t(eq? 5e100 5e100) ==> #t(eq? "racketeer" "racketeer") ==> #t。见docs.racket-lang.org/reference/…
        猜你喜欢
        • 2020-05-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多