功能编程:什么是“不正确的列表”?

时间:2009-12-17 02:18:02

标签: functional-programming erlang lisp scheme

有人可以解释什么是“不正确的清单”吗?

注意:谢谢大家!你们所有人都摇滚!

9 个答案:

答案 0 :(得分:61)

我认为@Vijay的答案是迄今为止最好的答案,我只是打算将其Erlangify。

Erlang中的对(cons单元格)写为[Head|Tail],nil写为[]。头部和尾部的含义没有限制,但如果使用尾部链接更多的细胞,则会得到列表。如果最终尾部为[],那么您将获得正确的列表。正确的列表

对列表有特殊的语法支持
[1|[2|[3|[]]]]

写成

[1,2,3]

和不正确的清单

[1|[2|[3|4]]]

写成

[1,2,3|4]

所以你可以看到差异。匹配正确/不正确的列表相应容易。所以正确列表的长度函数len

len([_|T]) -> 1 + len(T);
len([]) -> 0.

我们明确匹配终止[]。如果给出不正确的列表,则会产生错误。虽然返回列表最后一个尾部的函数last_tail也可以处理不正确的列表:

last_tail([_|T]) -> last_tail(T);
last_tail(Tail) -> Tail.                 %Will match any tail

请注意,像通常使用[Head|Tail]一样构建列表或对其进行匹配会检查尾部是否为列表,因此处理不正确的列表没有问题。虽然你可以用它们做很酷的事情,但很少需要不正确的列表。

答案 1 :(得分:40)

我认为使用Scheme解释这个更容易。

列表是一对以空列表结尾的对。换句话说,列表以cdr为()

的对结束
(a . (b . (c . (d . (e . ()))))) 

;; same as

(a b c d e)

不在空列表中结尾的一对链称为不正确的列表。请注意,不正确的列表不是列表。列表和点缀符号可以组合起来表示不正确的列表,如下面的等效符号所示:

 (a b c . d)
 (a . (b . (c . d)))

导致构建不正确列表的常见错误的一个例子是:

scheme> (cons 1 (cons 2 3))
(1 2 . 3)

注意(1 2 .3)中的点---就像(2.3)中的点一样,说一对的cdr指向3,而不是另一对或'()。也就是说,它是一个不合适的列表,而不仅仅是一对列表。它不适合列表的递归定义,因为当我们到达第二对时,它的cdr不是列表 - 它是一个整数。

Scheme打印出列表的第一部分,好像它是一个普通的cdr链接列表,但是当它结束时,它不能那样做,所以它使用了“点符号”。

您通常不需要担心点符号,因为您应该使用普通列表,而不是不正确的列表。但是如果你在Scheme打印出一个数据结构时看到一个意想不到的点,那么你很好地猜测你使用 cons 并给它一个非列表作为它的第二个参数 - 除了另一对或者()之外的东西

Scheme提供了一个方便的过程,可以创建适当的列表,称为 list list 可以接受任意数量的参数,并按照该顺序构造一个包含这些元素的正确列表。您不必记住提供空列表---列表会自动终止列表。

Scheme>(list 1 2 3 4)
(1 2 3 4)

礼貌:An Introduction to Scheme

答案 2 :(得分:9)

Erlang中列表的定义是in the manual - 特别是第2.10节

在Erlang中,你真正需要知道的关于不正确列表的唯一方法是如何避免它们,并且这样做的方法非常简单 - 这是你要建立列表的第一个“事物”上。以下都创建了正确的列表:

A = [].
B = [term()].
C = [term(), term(), term()].

在所有这些情况下, 语法 可确保隐藏的“空”尾部与最后的“[]”匹配。 ..

所以从他们那里,以下操作都产生了一个正确的列表:

X = [term() | A].
Y = [term() | B].
Z = [term() | C]. 

它们都是将新头添加到正确列表中的所有操作。

有用的是,您可以将XYZ中的每一个投放到以下函数中:

func([], Acc)      -> Acc;
func([H | T], Acc) -> NewAcc = do_something(H),
                      func(T, [NewAcc | Acc]).

当尾部的 隐藏 空列表完全剩下时,他们将翻阅列表并终止于顶部子句。

当您的基本列表制作不当时会出现问题,如下所示:

D = [term1() | term2()]. % term2() is any term except a list

此列表 隐藏 空列表作为终端尾,它有一个术语...

从罗伯特·维尔丁(Robert Virding)在评论

中指出,从这里向下是剁碎

那你怎么为它写一个终端条款?

令人愤怒的是 无法通过检查列表来查看列表是否不合适... 打印该死的东西看起来不错......所以你最终会创建一个不合适的基本列表,在它上面做一些事情,传递它,然后突然 kabloowie 你有一个距离错误的地方有一英里的距离,你拉扯你的头发和尖叫喊...

但是应该使用dialyzer为你嗅出这些小动物。

道歉

按照罗伯特的评论,我试图打印出一个不合适的清单,而且很明显:

(arrian@localhost)5>A = [1, 2, 3, 4].
[1,2,3,4]
(arrian@localhost)5> B = [1, 2, 3 | 4].
[1,2,3|4]
(arrian@localhost)6> io:format("A is ~p~nB is ~p~n", [A, B]).
A is [1,2,3,4]
B is [1,2,3|4]

我曾经花了一些时间去寻找一个不合适的名单,并说服自己这是不可能的,好吧阿肯诺

答案 3 :(得分:7)

要了解不正确的列表是什么,您必须首先了解正确列表的定义。

具体来说,列表的“整洁发现”是您可以仅使用具有固定数量元素的表单来表示列表,即:

;; a list is either 
;; - empty, or
;; - (cons v l), where v is a value and l is a list.

这个“数据定义”(使用如何设计程序的术语)有各种各样的 不错的属性。最好的一点是,如果我们在数据定义的每个“分支”上定义函数的行为或含义,我们保证不会错过一个案例。更重要的是,像这样的结构通常会产生漂亮干净的递归解决方案。

经典的“长度”示例:

(define (length l)
  (cond [(empty? l) 0]
        [else (+ 1 (length (rest l))]))

当然,Haskell的一切都比较漂亮:

length []    = 0
length (f:r) = 1 + length r

那么,这与不正确的名单有什么关系?

嗯,不正确的列表使用此数据定义,而不是:

;; an improper list is either
;; - a value, or
;; - (cons v l), where v is a value and l is an improper list

问题在于这个定义会导致含糊不清。特别是,第一和第二种情况重叠。假设我为不正确的列表定义“长度”:

(define (length l)
  (cond [(cons? l) (+ 1 (length (rest l)))]
        [else 1]))

问题是我已经破坏了一个不错的属性,如果我取两个值并将它们放入一个不正确的列表中(cons a b),结果的长度为2。为了理解原因,假设我考虑了值(cons 3 4)和(cons 4 5)。结果是(cons(cons 3 4)(cons 4 5)),可以将 解释为包含(cons 3 4)和(cons 4 5),的不正确列表作为包含(cons 3 4),4和5的不正确列表。

在具有更严格类型系统的语言(例如Haskell)中,“不正确列表”的概念并没有那么多意义;您可以将其解释为一种数据类型,其基本情况中包含两个内容,这可能不是您想要的。

答案 4 :(得分:4)

我认为它可能是指LISP中的“点对”,例如一个列表,其最终的cons单元格中有一个原子,而不是对cdr中另一个cons单元格或NIL的引用。

修改

维基百科建议循环列表也算不合适。参见

http://en.wikipedia.org/wiki/Lisp_(programming_language)

并搜索“不正确”并检查脚注。

答案 5 :(得分:4)

我想说一个不正确的列表的含义是列表的递归处理与典型的终止条件不匹配。

例如,假设您在Erlang中使用不正确的列表调用以下sum

sum([H|T]) -> H + sum(T);
sum([]) -> 0.

然后它会引发异常,因为最后一个尾部不是空列表,而是一个原子。

答案 6 :(得分:3)

在Common Lisp中,不正确的列表定义为:

  • 点名列表,其中包含非NIL终止'原子'。

实施例

  (a b c d . f)

  • 循环列表

实施例

  #1=(1 2 3 . #1#)

答案 7 :(得分:3)

列表由单元组成,每个单元由两个指针组成。第一个指向数据元素,第二个指向下一个单元格,或者在列表末尾指定nil。

如果第二个未指向单元格(或零),则列表不正确。函数式语言很可能允许您构造单元格,因此您应该能够生成不正确的列表。

在Erlang中(也可能在其他FP语言中),您可以通过将2元组存储为不正确的列表来节省一些内存:

2> erts_debug:flat_size({1,2}).
3
3> erts_debug:flat_size([1|2]).
2

答案 8 :(得分:2)

在Erlang中,正确的列表是[H|T]

H是列表的头部,T是列表的其余部分作为另一个列表。

不正确的列表不符合此定义。