我正在努力真正理解LISP,以便有一个良好的基础向前发展,但它一直很慢,因为LISP(在我的情况下特别是Common Lisp)不遵循任何C族命名约定。
这是我对LISP列表的个人定义,基本是否正确?: LISP中的所有列表都构造为单链表,使用void指针表示节点和下一个指针。
编辑:为了澄清,我使用“无效指针”这个词来表示关于cons-cell的CAR和CDR的性质的想法。我理解LISP中不存在“void指针”,我试图在这里将概念从C应用到LISP
答案 0 :(得分:7)
基本的Lisp数据结构,用C语言术语表示,可能如下所示:
/* A value is a "discriminated union". */
typedef struct value {
/* It has a type field. */
enum type { t_cons, t_symbol, t_fixnum, t_character /* , ... */ } type;
/* And then one of several payloads, overlaid in the same space,
which one being there depending on the type field. */
union {
struct symbol *sym;
struct cons *cons;
int fixnum; /* unboxed integer: no heap allocation */
/* ... */
} u;
} value;
/* This is the heap-allocated part of a cons cell;
not the complete cons cell value, which is actually
of "struct value" type. See cons()
function below which makes a cons value. */
struct cons {
struct value car, cdr;
};
static value nil = { t_symbol };
value cons(value a, value d)
{
value retv;
struct cons *c = allocate_cons(); /* from special cons heap */
c->car = a;
c->cdr = c;
retv.type = t_cons;
retv.u.cons = c;
return retv;
}
int is_nil(value v)
{
return (v.type == t_symbol && v.sym == NULL);
}
value cons(value a, value d)
{
struct value retv;
struct cons *c = allocate_cons(); /* from special cons heap */
c->car = a;
c->cdr = c;
retv.type = t_cons;
retv.u.cons = c;
return retv;
}
value car(value arg)
{
switch (arg.type) {
case t_cons:
return arg.u.cons->car;
case t_symbol:
if (is_nil(arg)) /* (car nil) -> nil */
return nil;
/* fallthrough */
default:
/* This function generates a Lisp exception somehow */
throw_error("car: not applicable to ~s", arg);
}
}
Lisp概念没有指定数据结构到这个细节。
实际的Lisp实现通常会为value
的更紧凑的表示做一些更聪明的事情。一种常见的技术是使用机器字(现在通常是指针大小)来获取Lisp值,并在该字中使用几个标记位来指示它是否是指向堆上某些东西的指针,或者直接表示整数。 (这意味着Lisp fixnum
整数不使用所有可用的32位或64位,但可能只使用30或62.较大的整数具有不同的类型bignum
,并且是堆分配的。)
然而,使用值而不是指针的结构为浮点值创建按值语义的机会,这是数字代码的胜利。这意味着浮点对象不必分配堆,而是存储在值中。
用C语言编写的Lisp实现可以做这种技巧,但它会导致ISO C未定义的行为,并且声明和代码不能用于说明目的。
使用这种类型的表示,一个很好的细节是对Lisp符号nil
使用C空指针。然后用C语言编写的任何内部例程都可以使用与Lisp相同的约定进行符合人体工程学的编写:nil
既是假的又是空列表。
C受Lisp的影响很大,因为它基于返回值的表达式,并且空指针为false。 C中的a?b:c
三元运算符有点像Lisp的(if a b c)
。{/ p>
需要很多C代码来引导类似Lisp的语义,并且有很多设计方案可供选择。因此,最好尝试将Lisp理解为抽象,而不是通过特定的详细数据结构和执行模型设计,更不用说用C表示。
答案 1 :(得分:1)
Common Lisp HyperSpec是标准,它描述了表单和函数的行为以及可能对复杂性的一些要求,但它从不干扰数据结构的低级实现是如何完成的。您可以使用不暴露任何硬件的语言实现CL,或者可以使用void *指针作为值来实现。
在许多实现中,虽然值是机器中的指针,因此void *完美地描述它。 Cons是一个包含两个机器单词的数组,car
和cdr
可以包含任何值。因此,它不仅仅是一个链表,除非您将其限制在您的使用中。您可以使用此结构制作树结构或建模任何类型的数据结构。
由于所有指针都是字节索引,而一个字通常是4或8字节,因此每个指针的2或3位始终为零。许多实现巧妙地在这些位中添加信息以便告诉指针代表什么。例如。如果lsb为1,则可能将地址解释为固定的有符号整数,并通过将其右移1位来获得它的实际值。 cons单元可能将所有标记位设置为2
。这就是两个fixnums可以eq
的原因。
现在读入一个正确的列表(1 2 3)
并打印出来,但如果您阅读(1 . (2 . (3 . ())))
,则会获得相同的输出。使用cons
的列表的幻觉嵌入在read
和write
函数中,以便在cdr
具有正确类型时正常工作。缺点可以包含任何值,但如果cdr
是cons
或nil
以外的其他任何值,则会以虚线表示。
答案 2 :(得分:0)
有助于将Lisp列表视为具有CAR中的值以及CDR或nil(空列表)中的列表的CONS对。您可以将任何列表视为其结构性CONSes。
e.g。 (list 1 2 3)
=> (cons 1 (cons 2 (cons 3 nil)))
答案 3 :(得分:0)
希望在Common Lisp Cookbook的新数据结构页面中有适合您观点的解释。
(简而言之,我不是专业人士,但我倾向于同意你的定义:列表是使用空指针完成的cons单元的延续)。
列表基本元素是cons单元格。我们通过组装来建立列表 利弊细胞。
(cons 1 2)
;; => (1 . 2) ;; representation with a point, a dotted pair.
看起来像这样:
[o|o]--- 2
|
1
如果第一个单元格的cdr
(rest
)是另一个cons单元格,并且cdr
为
最后一个是nil
,我们建立一个列表:
(cons 1 (cons 2 nil))
;; => (1 2)
看起来像这样:
[o|o]---[o|/]
| |
1 2
(作为draw-cons-tree的ascii art)。
看到表示不是虚线对? Lisp打印机 了解会议。
最后,我们可以使用list
简单地构建一个文字列表:
(list 1 2)
;; => (1 2)
或致电报价:
'(1 2)
;; => (1 2)
是特殊格式(quote (1 2))
的简写符号。