什么是eq?谓词在mit方案解释器中做什么?

时间:2016-10-11 05:04:23

标签: functional-programming scheme lisp

我知道只有当obj1和obj2都引用内存中的同一个对象时,public void setTransactionList(ArrayList<TransactionSum> newList) { mList = newList; notifyDataSetChanged(); } 才会返回(eq? obj1 obj2)

1)#t返回(eq? 2.6 2.6),因为它们是浮点数,并且浮点数的表示在方案中是不同的。有人可以解释它们在记忆中的表现吗?。

2)为什么对和非空字符串经常返回#f,即使我们之前没有声明它们?示例:#f,同时将相同数字与(eq? (cons 1 2) (cons 1 2))进行比较会得到与eq?谓词相同的结果。

例如,equal?会返回(define x 3) (define y 3) (eq? x y)

我也理解#t返回(eq? '() '()),因为内存中只有一个空列表表示,而且是指向0的指针。

1 个答案:

答案 0 :(得分:1)

要理解这一点,您真的需要了解Lisps通常如何表示对象。以下内容并非针对麻省理工学院计划,甚至可能不正确(或任何具体实施),但旨在让您了解一般的考虑因素。还有其他可能的表示,而不是下面描述的表示,但它们都有类似的权衡。

对象表示

首先,系统需要知道关于任何对象的两件事:

  • 它是什么类型 - 它的类型;
  • 它的价值是什么 - 代表对象的某种位模式。

当然,比特模式只在类型的上下文中有意义。

有许多类型,并且在许多Lisp方言中你可以制作新的类型,所以它不会起作用来说'只有这六种东西&#39;例如。但是有些类型比其他类型更重要(我会说&#39;某些类型比其他类型更平等&#39;):例如,你可能希望小整数非常有效。

好吧,在大多数情况下,你最终会得到一些指向对象实际数据的指针。但是现在你可以做一个聪明的技巧:大多数(所有?)现代机器都是字节寻址的 - 内存地址工作在8位的粒度。但是,如果您只想指向较大的对象,则指针中会有备用位。因此,如果您使用的是64位计算机并且只想指向64位边界上的内容,则指针的低3位从不使用,如果您只想指向128位边界你有4个备用位。

所以你可以做一个狡猾的伎俩:在一个64位的单词中,你可以打包一个指针,在你不需要的指针底部的几个位中,一个可以编码的指针几个最重要的类型。

你可以做得更好:对于一些小对象,你可以打包指针所在的对象本身。规范的例子是小整数 - Lisps历来称之为fixnums的东西。所以在你的话中,你现在有一个标记位模式,表示“这是一个固定的”。和fixnum的实际位,向右移动以为标签腾出空间。 (在实践中,你通常有两个标签,用于&#39;甚至fixnum&#39;奇怪的fixnum&#39;它让你做一个狡猾的技巧,使我更加大的固定点,我会留给你锻炼)。 Lisps传统上也会为NIL / ()执行此操作,传统上这可能是一个特殊的“零”。标签的其余部分为零。这可能对Scheme不太有意义,我不确定。

但是你通常不能这样做:大多数对象必须用指针存储,并且在最一般的情况下,单词中的标记位会说“它是其他的东西&” #39;,并且指针将指向某个具有事物类型和值的对象,并且将是几个字长。

像小整数一样立即存储的对象通常称为“未装箱”,而基于指针的一般对象则称为“装箱”。

eq?做什么

那么,eq?做了什么?简单:它告诉您两个字是否是相同的位模式:如果标记和指针/立即值都是相同的位。这非常快(单指令),但也非常原始。

对于诸如fixnums之类的直接类型,它会告诉您这两个对象是否具有相同的值。对于指针类型,它将告诉您两个对象是否相同:如果两个指针指向相同的地址(并且具有相同的标记位,但如果它们没有则会非常奇怪)。 赢得做的是告诉你两个指针类型是否代表相同的值,即使它们是不同的指针。

实习:一招

你可以为盒装类型做的一个技巧是实习它们:每次你要创建一个新的时候,你都要看看你之前是否已经制作了一个等效的,如果你有,你只需返回指向同一对象的指针。这意味着eq?将对任何两个等效的实习对象返回true,因为它们实际上是同一个对象。只有实习对象只有相对较少的数量,如果它们是不可变的或有效不可变的,或者你有意想要这种实习行为,那么它才真正有意义。

浮点数

浮点数为32位或64位。因此,在64位系统上,您可以立即表示一个32位的单个浮点数:您有一些标记位,一半字中有一堆零,另一半中有浮点。但你永远不能为双浮动做这件事。即使对于单个花车,你也需要一个特殊的漂浮物。标签和标签是一种极其稀缺的资源 - 可能只有8个。 (当然,大多数Lisps都是32位直到活内存,而32位Lisp甚至不能立即存储一个浮点数。)

因此,通常,单个浮动和双浮动都是盒装的。实习生浮标是没有意义的,因为你可以产生大量的实习浮标:每秒10亿或者其他东西,它们通常不一样。

所以eq?通常会在两个浮点数上返回false,即使它们在数值上相同。但是,你真的不能依赖它:这样的事情会怎样呢?

(define (x f)
  (let ([g f])
    (eq? g f)))
(x 1.0)

它可能会返回#t,但我不确定是否必须这样做。我很确定CL中的等价物是明确没有定义的,例如:允许实现复制浮点数。

最后,浮点计算通常是人们非常关心性能的地方,盒装花车会对性能产生很大影响。因此,通常使用合适的类型声明,可以编译将浮动视为立即,未装箱(和未标记)的性能对象的代码。然后,eq?可能会因数字相等的浮点数而返回true。但答案是,如果你想进行数字比较,请使用=:它是什么。

小整数

参见上面的讨论:eq?通常适用于小整数,因为它们会立即存储。但它对所有整数都不起作用:

> (eq? 1 1)
#t
> (eq? (expt 2 20) (expt 2 20))
#t
> (eq? (expt 2 64) (expt 2 64))
#f
> (eq? (expt 2 62) (expt 2 62))
#f
> (eq? (expt 2 61) (expt 2 61))
#t
> (eq? (- (expt 2 62) 1) (- (expt 2 62) 1))
#t

因此,对于此实现(Racket),您可以看到此处发生的未装箱整数与盒装整数之间的转换。

但据我所知,对于实现没有要求立即存储任何整数。再次:使用=,它是它的用途。

conses之外

Conses总是装箱,只有eq?才能相同。特别是,cons的工作是创建新的conses,因此(eq? (cons '() '()) (cons '() '())永远不会成真。

符号

符号是规范的实习类型:(eq 'x 'x)是真的,因为符号的作用是真的:当读取符号时,它被实习,并且读取具有相同名称的其他符号将(除了在奇怪的情况下)返回相同的相同对象。

我对Scheme中的符号知之甚少,但在CL中,您可以创建未加密的符号,尽管它们看起来相同但不是。这些对于CL中宏的事物名称很有用(Scheme有一个更聪明的方法)。

字符串

这些可能是实习,但可能只是其中的一部分,你不能依赖于此。所以,在Racket中,我认为你可能依赖(eq? "foo" "foo")是真的,但也许不是,你可能只能依赖它,如果你可以的话,因为这两个字符串来自读者。同样,你应该使用正确的谓词。

特殊类型

#t()之类的内容要么不是盒装的,要么是唯一的(这是生活中的重点),所以eq?会对它们起作用