为什么Haskell的`head`崩溃在一个空列表上(或者为什么*不*返回一个空列表)?(语言哲学)


76

其他潜在贡献者的注意事项:请毫不犹豫地使用抽象或数学符号来表达您的观点。如果我不确定您的答案,我会要求您进行说明,否则请随意以一种舒适的方式表达自己。

需要明确的是:我不是在寻找“安全”对象head,也不是head特别有意义的选择。问题的重点在于对head和的讨论head',后者旨在提供上下文。

我已经和Haskell纠缠了几个月了(以至于它已经成为我的主要语言),但是我承认我对某些更高级的概念或该语言的哲学细节并不了解(尽管我非常愿意学习)。那么,我的问题不只是技术性的问题(除非是技术性问题,我只是没有意识到),因为它是一种哲学。

对于此示例,我正在谈论head

我想像你会知道,

Prelude> head []    
*** Exception: Prelude.head: empty list

这来自head :: [a] -> a。很公平。显然,不能返回(挥舞着)no类型的元素。但是同时,定义起来很简单(如果不是很简单的话)

head' :: [a] -> Maybe a
head' []     = Nothing
head' (x:xs) = Just x

我见过一些这方面的讨论很少在这里的某些语句的注释部分。值得注意的是,一位Alex Stangl说

“有充分的理由不使一切都“安全”,并在违反前提条件时抛出异常。

我不一定要质疑这个主张,但是我对这些“好的理由”是什么感到好奇。

另外,保罗·约翰逊(Paul Johnson)说,

“例如,您可以定义“ safeHead :: :: [a]->可能是一个”,但是现在除了处理一个空列表或证明它不会发生之外,您必须处理“ Nothing”或证明它不会发生。”

我从那条评论中读到的语气表明,这在难度/复杂性/事物方面有明显的增加,但是我不确定我是否理解他在这里提出的内容。

一位史蒂文·普鲁兹纳(Steven Pruzina)说(在2011年不少),

“有一个更深层的原因,例如'head'不能防崩溃。要实现多态但要处理一个空列表,'head'必须始终返回任何特定空列表中都没有的类型的变量。如果Haskell能够做到这一点,那就太好了……”。

通过允许空列表处理,多态性会丢失吗?如果是这样,怎么回事,为什么?是否有某些特殊情况使这一点显而易见?此部分由@Russell O'Connor充分回答。任何进一步的想法,当然,表示赞赏。

我将根据明确性和建议的要求对其进行编辑。您可以提供的任何想法,论文等,将不胜感激。


'head'既不安全(不完整,也不完整),也不抛出异常(对于空列表未定义)。
旅鼠

Answers:


101

通过允许空列表处理,多态性会丢失吗?如果是这样,怎么回事,为什么?是否有某些特殊情况使这一点显而易见?

状态的自由定理head

f . head = head . $map f

应用该定理[]意味着

f (head []) = head (map f []) = head []

这个定理必须适用于每个定理f,因此特别必须适用于const Trueconst False。这意味着

True = const True (head []) = head [] = const False (head []) = False

因此,如果head适当地是多态的并且head []是一个总值,True则将等于False

PS。关于您的问题的背景,我还有其他评论,以达到以下效果:如果您具有列表非空的先决条件,则应通过在函数签名中使用非空列表类型而不是列表来强制实施它。


1
那只是我一直在寻找的答案。如果您愿意提出其他意见,我们也很期待。:)
Jack Henahan 2011年

2
我只是免费查找了Wadler定理!有关自由定理的更多阅读。
Jack Henahan 2011年

21
我不明白为什么这会导致问题head :: [a] -> Maybe a,它有一个自由定理fmap f . head = head . map f,这样你就会知道Nothing == Nothing。除非您只是试图说明为什么不可能有一个default :: forall a . a可以返回的值,head []并且我们可以进行模式匹配...,但是有更简单的方法可以讨论这一点。
J. Abrahamson

@ J.Abrahamson cf Data.List.Safe
bjd2385

25

为什么有人使用head :: [a] -> a而不是模式匹配?原因之一是因为您知道参数不能为空,并且不想编写代码来处理参数为空的情况。

当然,您head'的类型[a] -> Maybe a在标准库中定义为Data.Maybe.listToMaybe。但是,如果您将headwith替换为use listToMaybe,则必须编写代码以处理空情况,这违背了using的目的head

我并不是说使用head是一种好风格。它掩盖了它可能导致异常的事实,从这个意义上说,这是不好的。但这有时很方便。关键是要达到head某些目的,而这是不能实现的listToMaybe

问题中的最后一个引号(关于多态性)仅意味着不可能定义类型函数以[a] -> a返回空列表中的值(如Russell O'Connor在其回答中所述)。


8

期望以下内容成立xs === head xs : tail xs是很自然的:-列表与其第一个元素相同,然后是其余元素。看起来合乎逻辑,对不对?

现在,让我们来算conses之外的(应用程序的数量:),不考虑实际因素,运用声称“法律”的时候[][]应该是相同的foo : bar,但前者0 conses之外,而后者则有(至少一个)。哦,这里不对劲!

Haskell的类型系统尽管具有所有优点,却不能表达您只应调用head非空列表的事实(“法律”仅对非空列表有效)。使用head将举证责任转移给程序员,程序员应确保不会将其用于空列表。我相信像Agda这样的依赖类型语言可以在这里提供帮助。

最后,从操作哲学的角度进行一些描述:应如何head ([] :: [a]) :: a实施?a凭空想出类型的值是不可能的(想像无人居住的类型,例如data Falsum),就等于证明了一切(通过Curry-Howard同构)。


4
“将足以证明任何事情(通过Curry-Howard同构)”。这是一个非常有趣的观点。我没有考虑过这种联系。说得好。
Jack Henahan 2011年

4

有很多不同的方式来考虑这一点。因此,我将赞成和反对head'

反对head'

不需要head':由于列表是一种具体的数据类型,因此您可以head'通过模式匹配来完成所有操作。

此外,head'您只需要用一个函子换另一个函子即可。在某个时候,您希望精打细算,对基础列表元素进行一些工作。

捍卫head'

但是模式匹配掩盖了正在发生的事情。在Haskell中,我们对计算函数很感兴趣,可以通过使用组合和组合器以无点样式编写函数来更好地完成函数。

此外,考虑[]Maybe函数,head'可以在它们之间来回移动(特别Applicative[]with的实例pure = replicate)。


虽然,从某种逻辑上说,根本没有必要head,但它仍然存在。总体而言,我更喜欢您的回答,并且我会对您对提出的观点有任何其他想法感兴趣。
Jack Henahan 2011年

实际上,有些时候我想要,head而有些时候我希望我拥有(或者我刚刚定义)head'
2011年

3
@MtnViewMark如果head'需要,您只需要关注Data.Maybe.listToMaybe
罗素·奥康纳

也许head'' :: MonadPlus m => [a] -> m a会更有用。
chaosmasttter 2014年

3

如果在您的用例中空列表根本没有意义,那么您始终可以选择NonEmptyneHead安全的地方使用它。如果从那个角度看,它不是head不安全的功能,而是整个列表数据结构(同样,对于该用例)。


1

我认为这是简单和美观的问题。当然,这是情人眼中的。

如果来自Lisp背景,您可能会知道列表是由cons单元构成的,每个单元都有一个数据元素和一个指向下一个单元的指针。空列表本身不是列表,而是特殊符号。Haskell遵循这种推理。

在我看来,如果空列表和列表是两个不同的东西,则既干净,推理起来也更传统。

...我可能会补充-如果您担心头部不安全-请不要使用它,而应使用模式匹配:

sum     [] = 0
sum (x:xs) = x + sum xs

2
哦,当然,我总是喜欢模式匹配head。与其说是关注,不如说是好奇心。再谈您的其余评论:传统意义何在?谁的传统?为什么是传统?
Jack Henahan 2011年

好吧,函数式编程的传统。我可能必须立即吃完自己的话-在Lisp中,(car'())返回NIL。但是Haskell只是试图统一现代函数式编程人群。我相信OCaml和Miranda都会在空名单上出现错误。
约翰·科特林斯基2011年

我正试图合理化历史的好奇心。大致(非常粗略)[]相当于空集。在数学上下文中,空集只是其中没有任何内容集。同样,[]它仍然是一个列表,实际上应该没有元素(可能没有类型)的列表。例如,如果这是类型化lambda演算的限制,则可能有助于解释的处理[],但我不确定情况是否如此。
Jack Henahan 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.