Functional programming 函数式编程:什么是“函数式编程”;不正确的列表;?

Functional programming 函数式编程:什么是“函数式编程”;不正确的列表;?,functional-programming,erlang,lisp,scheme,Functional Programming,Erlang,Lisp,Scheme,有人能解释一下什么是“不当清单”吗 注意:谢谢大家!你们都很摇滚 我认为它可能是指LISP中的“点对”,例如,在cdr中,其最后一个cons单元格有一个原子的列表,而不是对另一个cons单元格或NIL的引用 编辑 维基百科建议,循环列表也被视为不合适。看 然后搜索“不适当”并检查脚注。我想说,不适当列表的含义是,对列表的递归处理将不符合典型的终止条件 例如,假设您在一个不正确的列表中调用以下sum,使用Erlang: sum([H|T]) -> H + sum(T); sum([]) -

有人能解释一下什么是“不当清单”吗


注意:谢谢大家!你们都很摇滚

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

编辑

维基百科建议,循环列表也被视为不合适。看


然后搜索“不适当”并检查脚注。

我想说,不适当列表的含义是,对列表的递归处理将不符合典型的终止条件

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

sum([H|T]) -> H + sum(T);
sum([]) -> 0.
X = [term() | A].
Y = [term() | B].
Z = [term() | C]. 

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

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

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

;; a list is either 
;; - empty, or
;; - (cons v l), where v is a value and l is a list.
这种“数据定义”(使用如何设计程序的术语)具有各种各样的特性 很好的性能。其中一个最好的方法是,如果我们在数据定义的每个“分支”上定义函数的行为或含义,就可以保证不会遗漏任何一个案例。更重要的是,像这样的结构通常会产生干净的递归解决方案

经典的“长度”示例:

当然,哈斯克尔的一切都更美好:

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]))
问题是我破坏了nice属性,如果我取两个值并将它们放入一个不正确的列表(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),“不正确列表”的概念没有那么有意义;您可以将其解释为一种数据类型,其基本大小写中包含两个内容,这可能也不是您想要的。

在Erlang中,适当的列表是其中的
[H | T]

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


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

我认为使用此方案更容易解释

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

不以空列表结尾的对链称为不正确列表。请注意,不正确的列表不是列表。列表和虚线符号可以组合表示不正确的列表,如下等效符号所示:

 (a b c . d)
 (a . (b . (c . d)))
导致构建不当列表的常见错误示例如下:

scheme> (cons 1 (cons 2 3))
(1 2 . 3)
注意(12.3)中的点——这与(2.3)中的点类似,表示一对的cdr指向3,而不是另一对或'()。也就是说,这是一个不恰当的列表,而不仅仅是一个成对的列表。它不符合列表的递归定义,因为当我们到达第二对时,它的cdr不是列表——它是一个整数

Scheme打印出列表的第一部分,就好像它是一个普通的cdr链表一样,但当它到达末尾时,它不能这样做,所以它使用了“点表示法”

您通常不需要担心点符号,因为您应该使用正常的列表,而不是不正确的列表。但如果在Scheme打印数据结构时看到一个意外的点,那么很有可能使用cons并给它一个non-list作为第二个参数——除了另一对或()之外的其他参数

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

Scheme>(list 1 2 3 4)
(1 2 3 4)
礼貌:

在通用Lisp中,不正确列表定义为:

  • 具有非零终止“atom”的点列表
范例

  (a b c d . f)
  #1=(1 2 3 . #1#)

  • 循环列表
范例

  (a b c d . f)
  #1=(1 2 3 . #1#)

Erlang中的列表定义见第2.10节

在Erlang中,对于不正确的列表,您真正需要知道的唯一一件事就是如何避免它们,而方法非常简单——这完全取决于您要构建列表的第一件“事情”。以下所有选项都可以创建适当的列表:

A = [].
B = [term()].
C = [term(), term(), term()].
2> erts_debug:flat_size({1,2}).
3
3> erts_debug:flat_size([1|2]).
2
len([_|T]) -> 1 + len(T);
len([]) -> 0.
在所有这些情况下,语法确保有一个隐藏的“空”尾,与结尾处的“[]”排序相匹配

因此,以下操作都可以从中生成一个适当的列表:

sum([H|T]) -> H + sum(T);
sum([]) -> 0.
X = [term() | A].
Y = [term() | B].
Z = [term() | C]. 
它们都是将新的头添加到适当列表中的操作

有用的是,您可以将
X
Y
Z
中的每一项输入到如下函数中:

func([], Acc)      -> Acc;
func([H | T], Acc) -> NewAcc = do_something(H),
                      func(T, [NewAcc | Acc]).
当尾部的hidden空列表剩下时,它们将撕开列表并在顶部子句终止

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

D = [term1() | term2()]. % term2() is any term except a list
此列表没有隐藏的空列表作为终端尾部,它有一个术语

正如罗伯特·维丁(Robert Virding)在评论中指出的那样,从这里开始往下看就是肉末 那么,如何为它编写一个终止子句呢

让人恼火的是,无法通过检查列表来判断列表是否不正确…打印该死的t
[1|[2|[3|4]]]
[1,2,3|4]
len([_|T]) -> 1 + len(T);
len([]) -> 0.
last_tail([_|T]) -> last_tail(T);
last_tail(Tail) -> Tail.                 %Will match any tail