根据Haskell 2010报告,init定义如下:
init :: [a] -> [a]
init [x] = []
init (x:xs) = x : init xs
init [] = error "Prelude.init: empty list"
Run Code Online (Sandbox Code Playgroud)
base-4.4.1.0对它进行了类似的定义.对我而言,似乎完全可以接受并且直观地拥有:
init [] = []
Run Code Online (Sandbox Code Playgroud)
这将init完全发挥作用.由于这个定义进入了haskell 2010报告,我想它有争议.是这种情况还是由于传统和向后兼容性而定义的?
ehi*_*ird 19
同样的原因tail []不是[]; 它打破了定义这些函数的不变量.我们可以定义head并tail说明if head和tail是否定义了值xs,然后xs ? head xs : tail xs.
正如尼古拉斯指出,我们希望不变的定义init和last是xs ? init xs ++ [last xs].确实last没有在空列表中定义,但last如果init不能定义为什么应该定义?就像输入中的一个head或被tail定义而另一个不是,init并且last是同一个硬币的两个边是错误的,将列表分成两个值,它们一起等同于原始值.
对于一个更"实用"的视图(虽然能够根据有用的不变量来推断程序,但是它们使用的操作具有非常实际的好处!),使用的算法init可能在空列表上行为不正确,如果init有效这样,它可以隐藏产生的错误.最好init保守其输入,以便在使用时必须考虑像空列表这样的边缘情况,尤其是当它完全不清楚它在这些情况下返回的建议值是否合理时.
这里有一个以前的堆栈溢出问题有关head,并tail包括为什么tail []不只是返回[]; 那里的答案应该有助于解释为什么这些不变量如此重要.
Nik*_* B. 11
因为我觉得这个问题很有意思,所以我决定写下我对这个问题的看法:
合乎逻辑
假设我们定义init xs为"列表,如果你放在last xs它的末尾,它等于xs.这相当于:init xs是没有最后一个元素的列表.因为xs == [],没有最后一个元素,所以init []必须是未定义的.
您可以为此添加一个特殊情况,例如"if xs是空列表,然后init xs是空列表,否则init xs就是列表,如果放在last xs它的末尾,则等于xs ".注意这是多么冗长和不太干净.我们引入了额外的复杂性,但是为什么?
直观的
init [1,2,3,4] == [1,2,3]
init [1,2,3] == [1,2]
init [1,2] == [1]
init [1] == []
init [] == ??
Run Code Online (Sandbox Code Playgroud)
注意方程右侧列表的长度如何随左侧的长度减小.对我来说,这个系列不能以合理的方式继续,因为右侧的列表必须具有负的大小!
实际的
正如其他人所指出的那样,定义一个特殊的办案为init或tail为空列表作为参数,可以介绍的情况下难以当场错误,其中功能可以对空列表没有合理的结果,但仍然不产生它的例外!
此外,我可以想到没有算法实际上有利于init []评估[],那么为什么要引入额外的复杂性呢?编程完全是关于简单性的,尤其是Haskell都是关于纯度的,你不同意吗?
这里真正的问题是init,如所定义的那样,不可能奏效; 它本身就是部分功能,就像head和tail.关于标准库中存在多少故意部分函数,我并不感到兴奋,但这是另一回事.
由于init无法挽救,我们有两种选择:
理解它是给定列表上的过滤器,保留所有不是最后一个元素的元素,并放弃关于length和的不变量last.这可以写成reverse . drop 1 . reverse,这表明一个明显的概括.take如果需要,我们也可以引入类似的对应物.
识别init定义部分函数,并给它正确的类型,[a] -> Maybe [a].然后可以正确替换错误Nothing.
这将是可怕的有init [] = [],尤其是它会破坏重要的不变量像xs == init xs ++ [last xs]和length xs == 1 + length (init xs),因此隐藏的错误.