【发布时间】:2018-11-15 15:38:15
【问题描述】:
在具有向量的 Lisps 中,为什么仍然需要 cons 单元格?据我了解,一个缺点单元格是:
- 只有 2 个元素的结构
- 已订购
- 访问是 O(1)
不过,所有这些也适用于 2 向量。那么有什么区别呢? cons 细胞只是 Lisps 有载体之前的遗迹吗?还是有其他我不知道的差异?
【问题讨论】:
标签: lisp common-lisp
在具有向量的 Lisps 中,为什么仍然需要 cons 单元格?据我了解,一个缺点单元格是:
不过,所有这些也适用于 2 向量。那么有什么区别呢? cons 细胞只是 Lisps 有载体之前的遗迹吗?还是有其他我不知道的差异?
【问题讨论】:
标签: lisp common-lisp
虽然在物理上,conses 类似于任何其他二元素聚合结构,但它们不仅仅是 2-vector 的过时形式。
首先,Lisp 中的所有类型都分为cons 和atom。只有 conses 的类型为 cons;其他一切都是原子。向量就是原子!
Conses 构成了嵌套列表的表示基础,它当然用于编写代码。它们有一个特殊的打印符号,这样(cons 1 (cons 2 nil)) 生成的对象方便地打印为(1 2),(cons 1 (cons 2 3)) 生成的对象打印为(1 2 . 3)。
cons 与atom 的区别在语法中很重要,因为满足consp 测试的表达式被视为复合形式。而不是关键字符号的原子,t 或 nil 会评估自己。
要获取列表本身而不是复合表单的值,我们使用quote,我们有一个很好的速记。
拥有一个不会以这种方式与评估语义纠缠在一起的向量类型很有用:其实例只是自评估原子。
Cons 细胞不是 Lisps 有向量之前的遗迹。首先,几乎没有这样的时间。 1960 年的 Lisp 1 手册已经描述了数组。其次,自那时以来的新方言仍然有缺点。
具有相似表示的对象不仅仅是彼此冗余。类型区分很重要。例如,我们不会仅仅因为以下两个都具有三个插槽就认为它们彼此是多余的:
(defstruct name first initial last)
(defstruct bank-transaction account type amount)
在 TXR Lisp 方言中,我曾经有过这样的语法糖 a..b 表示 (cons a b) 用于范围。但这意味着范围是consp,由于对列表的模糊性,这很愚蠢。我最终将其更改为a..b 表示(rcons a b):构造范围对象的表单,打印为#R(x y)。 (并且可以以这种方式指定为文字)。这产生了有用的细微差别,因为我们可以区分函数参数是范围 (rangep) 还是列表 (consp)。就像我们关心某个对象是bank-transaction 还是name。 Range 对象的表示方式与 conses 完全相同,并从相同的堆中分配;只是它们具有不同的类型,这使它们成为原子。如果评估为表单,它们会评估自己。
基本上,我们必须将类型视为额外的插槽。双元素向量确实(至少)具有三个属性,而不仅仅是两个:它具有第一个元素、第二个元素和一个类型。向量 #(1 1) 与 cons 单元格 (1 . 1) 的不同之处在于它们都具有第三个方面,类型,这是不一样的。
一个对象与所有其他同类对象共享的不可变属性仍然可以被视为“槽”。实际上,所有对象都有一个“类型槽”。所以 conses 实际上是具有car、cdr 和type 的三属性对象:
(car '(a . b)) -> A
(cdr '(a . b)) -> B
(type-of '(a . b)) -> CONS
她是第四个“槽”:
(class-of '(a . b)) -> #<BUILT-IN-CLASS CONS>
我们不能仅根据在堆上分配的每个实例的存储向量来查看对象。
顺便说一句,1960 年代的 MacLisp 方言将 cons 的概念扩展到具有更多命名字段的固定大小的聚合对象(除了 car 和 cdr):cxr-s。这些对象被称为“hunks”并且是documented in Kent Pitman's MacLisp manual。 Hunks 不满足谓词consp,而是hunkp;即它们被认为是原子。但是,它们用多个点扩展了 cons 符号。
【讨论】:
在一个典型的 Common Lisp 实现中,一个 cons 单元将被表示为“两个机器字”(一个用于汽车指针,一个用于 cdr 指针;它是一个 cons 单元这一事实被编码在构造为引用的指针中它)。但是,数组是更复杂的对象,除非您有一个专用的“T 类型的仅包含两个元素的向量”,否则您最终会得到一个包含类型信息和大小的数组标题,以及存储元素所需的存储空间(可能很难压缩到少于“四个机器词”)。
因此,尽管使用二元素向量/数组作为 cons 单元是非常有可能的,但基于现有 Lisp 中经常使用 cons 单元和列表这一事实,使用专用类型可以提高效率代码。
【讨论】:
我认为它们是不同的数据结构,例如 java 有向量和列表类。一种适合随机访问,列表更适合顺序访问。所以在任何语言中向量和列表都可以共存。
对于使用您的方法实现 Lisp,我相信它是可行的,这取决于您的实现细节,但对于 ANSI Common Lisp,有一个约定,因为没有列表数据类型:
CL-USER> (type-of (list 1 2 3))
CONS
这是一个 CONS,约定与此类似(查看常见的 lisp hypersec):
列表 n。 1. conses 链,其中每个 cons 的 car 是列表的一个元素,每个 cons 的 cdr 要么是列表中的下一个链接 链或终止原子。另请参阅正确列表、虚线列表或 循环列表。 2. null 和 cons 并集的类型。
因此,如果您使用向量而不是 cons 创建 Lisp,它将不是 ANSI CL
因此您可以创建“consing”事物的列表,nil 是一个列表,您可以使用 consing 创建不同类型的列表:
通常你会创建一个合适的列表:
(list 1 2 3) = (cons 1 (cons 2 (cons 3 nil)))) = '(1 2 3)
当列表不以 nil 结尾时,它是一个点列表,并且一个循环列表具有对自身的引用
例如,如果我们创建一个字符串 common lisp,将其实现为一个简单数组,这对于随机访问比列表更快
CL-USER> (type-of "this is a string")
(SIMPLE-ARRAY CHARACTER (16))
Land of lisp(一本关于 common lisp 的好书)将 cons 定义为构建 common lisp 和处理列表的粘合剂,所以当然,如果您将 cons 替换为其他类似的东西,您将构建类似于 common lisp 的东西。
终于出一棵常见lisp序列类型的树,你可以找到here完整
【讨论】:
type-of 会在 cons 单元格/列表上给他们'cons,但他们也可以使用typep 和list。例如。 (typep '(1 2 3) 'list) 或 (typep (cons 1 2) 'list)。 (typep '(1 2 3) 'cons) 和 (typep (cons 1 2) 'cons) 也会给 t。
cons 细胞只是 Lisps 有向量之前的遗迹吗?
没错。 cons、car、cdr 是第一个 lisp 中唯一复合数据结构的构造函数和访问函数。为了将它与唯一的原子类型符号区分开来,一个有atom 的符号是T,但对于cons 是错误的。这已扩展到 Lisp 1.5 中的其他类型,包括向量(称为数组,请参见第 35 页)。 Common Lisp 是基于 lisp 1.5 的商业 lisp 的组合。如果这两种类型都是从一开始就制造的,也许它们会有所不同。
如果您要制作一个 Common Lisp 实现,您不需要有两种不同的方法来制作它们,只要您的实现符合规范即可。如果我没记错的话,我认为球拍实际上是用向量实现 struct 并且 vector? 被重载为 #f 对于确实代表一个对象的向量。在 CL 中,您可以以相同的方式实现 defstruct 并实现 cons 结构和它需要与超规范兼容的功能。当您在自己喜欢的实现中创建 cons 时,您可能正在使用向量,甚至不知道它。
从历史上看,您仍然拥有旧功能,因此即使在第一个 lisp 出现 58 年后,John McCarthy 代码仍然可以工作。没有必要,但在具有现代语言功能的语言中保留一点遗产并没有什么坏处。
【讨论】:
如果您使用二元素向量,您会将它们的大小(和类型)存储在列表的每个节点中。
这是荒谬的浪费。
您可以通过引入一种特殊的 2 元素向量类型来避免这种浪费,该类型的元素可以是任何元素。
或者换句话说:通过重新引入 cons 单元格。
【讨论】:
一方面这是一个“实现细节”:给定向量,可以使用长度为 2 的向量来实现 cons 单元(以及链表)。
另一方面,这是一个相当重要的细节:ANSI Common Lisp 标准规定 vector 和 cons 类型是不相交的,所以事实上,您不能使用这个技巧实现 ANSI CL。
【讨论】: