根据Haskell 2010 report,init
定义如下:
init :: [a] -> [a]
init [x] = []
init (x:xs) = x : init xs
init [] = error "Prelude.init: empty list"
base-4.4.1.0同样定义它。对我来说,似乎完全可以接受和直观地说:
init [] = []
这将使init
成为一个完整的功能。由于这个定义进入了haskell 2010报告,我想它有争议。是这种情况还是由于传统和向后兼容性而定义的方式?
答案 0 :(得分:19)
同样的原因tail []
不是[]
;它打破了定义这些函数的不变量。我们可以定义head
和tail
,如果为head
定义了tail
和xs
,那么xs ≡ head xs : tail xs
。
正如Niklas所指出的,我们想要定义init
和last
的不变量是xs ≡ init xs ++ [last xs]
。确实,last
也没有在空列表中定义,但如果last
不能定义,为什么要定义init
?就像在输入中定义head
或tail
之一而另一个未定义那样是错误的,init
和last
是同一枚硬币的两面,将列表拆分为两个值,这两个值一起等同于原始值。
对于更“实用”的视图(尽管能够根据有用的不变量对程序进行推理,但它们使用非常实际的好处!),一种使用{{1}的算法可能在空列表上行为不正确,如果init
以这种方式工作,它将隐藏产生的错误。 init
最好保守其输入,以便在使用时必须考虑像空列表这样的边缘情况,特别是当它完全不清楚它的返回建议值时在那些情况下是合理的。
这是关于init
和head
的{{3}},其中包括tail
不仅仅返回tail []
的原因;那里的答案应该有助于解释为什么这些不变量如此重要。
答案 1 :(得分:11)
因为我觉得这个问题很有意思,所以我决定写下我对这个问题的想法:
<强>逻辑强>
假设我们将init xs
定义为“列表,如果您将last xs
放在其末尾,则等于 xs 。这相当于:{{1没有最后一个元素的列表。对于init xs
,不存在最后一个元素,因此xs == []
必须是未定义的。
您可以为此添加一个特殊情况,例如“if xs 是空列表,然后init []
是空列表,否则init xs
是列表,如果您将init xs
放在最后,则等于 xs “。注意这是多么冗长和不太干净。我们引入了额外的复杂性,但是对于什么?
<强>直观强>
last xs
注意方程右侧列表的长度如何随左侧长度减小。对我来说,这个系列不能以合理的方式继续,因为右侧的列表必须具有负的大小!
<强>实践强>
正如其他人所指出的那样,为空列表定义init [1,2,3,4] == [1,2,3]
init [1,2,3] == [1,2]
init [1,2] == [1]
init [1] == []
init [] == ??
或init
的特殊案例处理作为参数,可能会在函数无法理解的情况下引入难以处理的错误空列表的结果,但仍然不会产生异常!
此外,我认为没有算法让tail
评估为init []
实际上是有利的,那么为什么要引入额外的复杂性呢?编程就是简单性,特别是Haskell都是关于纯度的,你不同意吗?
答案 2 :(得分:8)
这里真正的问题是init
,如定义的那样,不可能奏效;这是一个固有的部分功能,就像head
和tail
一样。关于标准库中存在多少故意部分函数,我并不感到兴奋,但这是另一回事。
由于无法挽救init
,我们有两个选择:
解释它是给定列表中的过滤器,保留所有不是最后一个元素的元素,并放弃与length
和last
相关的不变量。这可以写成reverse . drop 1 . reverse
,这表明了一个明显的概括。如果需要,我们也可以为take
引入类似的对应物。
认识到init
定义了部分函数,并为其指定了正确的类型[a] -> Maybe [a]
。然后可以使用Nothing
答案 3 :(得分:5)
拥有init [] = []
会很糟糕,特别是它会破坏xs == init xs ++ [last xs]
和length xs == 1 + length (init xs)
之类的重要不变量,从而隐藏错误。
答案 4 :(得分:1)
这是传统的。当时,他们做出了任意选择,现在人们已经习惯了。
他们使init
与last
类似,因此他们都在空列表中失败。 (tail
和head
是另一对。)
tail' = drop 1
,以及匹配的init'
函数,允许空列表。我不认为这会有任何问题 - 我不相信所有关于不变量和自由定理的讨论。这只是意味着init'
与其同伴last
有点不同;就是这样。
(last
和head
是另一个故事:他们的签名是[a] -> a
,所以他们必须回馈一个元素,他们不能凭空创造一个。 ,init
和tail
的类型当然是[a] -> [a]
,这意味着他们可以并且确实会产生空列表。)