为什么init是部分功能?

时间:2011-12-21 14:24:55

标签: haskell

根据Haskell 2010 reportinit定义如下:

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报告,我想它有争议。是这种情况还是由于传统和向后兼容性而定义的方式?

5 个答案:

答案 0 :(得分:19)

同样的原因tail []不是[];它打破了定义这些函数的不变量。我们可以定义headtail,如果为head定义了tailxs,那么xs ≡ head xs : tail xs

正如Niklas所指出的,我们想要定义initlast的不变量是xs ≡ init xs ++ [last xs]。确实,last也没有在空列表中定义,但如果last不能定义,为什么要定义init?就像在输入中定义headtail之一而另一个未定义那样是错误的,initlast是同一枚硬币的两面,将列表拆分为两个值,这两个值一起等同于原始值。

对于更“实用”的视图(尽管能够根据有用的不变量对程序进行推理,但它们使用非常实际的好处!),一种使用{{1}的算法可能在空列表上行为不正确,如果init以这种方式工作,它将隐藏产生的错误。 init最好保守其输入,以便在使用时必须考虑像空列表这样的边缘情况,特别是当它完全不清楚它的返回建议值时在那些情况下是合理的。

这是关于inithead的{​​{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,如定义的那样,不可能奏效;这是一个固有的部分功能,就像headtail一样。关于标准库中存在多少故意部分函数,​​我并不感到兴奋,但这是另一回事。

由于无法挽救init,我们有两个选择:

  • 解释它是给定列表中的过滤器,保留所有不是最后一个元素的元素,并放弃与lengthlast相关的不变量。这可以写成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)

这是传统的。当时,他们做出了任意选择,现在人们已经习惯了。

他们使initlast类似,因此他们都在空列表中失败。 (tailhead是另一对。)

但是,正如你的建议,他们本可以选择相反的方式。 Prelude可以很容易地包含函数tail' = drop 1,以及匹配的init'函数,允许空列表。我不认为这会有任何问题 - 我不相信所有关于不变量和自由定理的讨论。这只是意味着init'与其同伴last有点不同;就是这样。

lasthead是另一个故事:他们的签名是[a] -> a,所以他们必须回馈一个元素,他们不能凭空创造一个。 ,inittail的类型当然是[a] -> [a],这意味着他们可以并且确实会产生空列表。)