Haskell错误信息:加油

2022-02-17 16:10:29

我非常喜欢强类型语言,我最喜欢的GC语言是Haskell。我希望读者今天记住这一点。我写的是一些关于我深爱的语言的评论,一些充满爱的批评。

事情是这样的:几天前,我向一位主要使用Python编程的朋友展示了一些Haskell。风险很高——我能证明这种奇怪的语言值得调查吗?

我的主要关注点是无限列表,并将斐波那契定义为递归数据结构——所有这些都是展示哈斯克尔懒惰的有趣事情。但在某个时刻,我们意外地编写了一个表达式,其中有一个类型错误,因此我们了解了编译器是如何处理这些事情的。我不记得确切的表达式了——它在上下文中很深——但问题是我试图在列表中添加一个整数。与1+[2,3]有关的内容。

现在,在一些“弱类型”语言中,这种事情实际上是允许的,我的一位同事最近指出:

这当然很搞笑。但是我们不应该用如此宽泛的笔触描绘“弱类型”语言。在我朋友的原生Python中,这应该是一个错误。这是一个运行时错误,但当您使用解释性语言编写临时脚本时,这又有什么关系呢。重要的是,失败被视为失败,它不会继续胡说八道:

[jim@palatinate[~] Python 3Python 3.810(默认值,11月26日2021,20:14:08)[GCC 93.0]在LimuxType和γ34;帮助""版权所有""学分";或";执照和#34;了解更多信息>>>;1+[2,3]回溯(最后一次调用):文件和#34&书信电报;stdin>", 第1行,in<;模块>;TypeError:不支持+:'的操作数类型;int';和';名单';

这是一条错误消息。这甚至是一条相当不错的错误信息。在Python中,有很多东西可以传递给+运算符,但int和list不在其中。

那么现在,哈斯克尔做了什么,我想炫耀的语言?不幸的是,我的朋友没有看到代码中的实际问题,但首先是从编译器的错误消息中得知的。如果你以前在Haskell做过这件事,你现在可能会退缩,因为你知道这个错误信息是什么:

[jim@palatinate:~]$ghciGHCi,8.6.5版:http://www.haskell.org/ghc/ :? 帮助前奏>;1+[2,3]<;交互式>;:1:1:错误:•约束中的非类型变量参数:Num[a](使用FlexibleContexts允许此操作)•检查推断的类型时,它::forall a.(Num a,Num[a])=>;[a]

现在,我的朋友根本不理解这个错误信息。由于我处于演示模式,我的本能是向他解释,但在几个错误的开始之后,我意识到这根本没有帮助,并指出不能向列表中添加整数,并向他展示了发生这种情况的地方(这比这个例子要微妙一些)。

但从那以后,我和同事们一直在讨论拉克的错误信息,特别是Rust的错误信息有多好,特别是它们比Haskell的好多少。所以我有机会分享我和我的朋友发现的非常糟糕的Haskell错误信息。在那里,它作为一个案例研究,所以我们可以讨论它在多大程度上是不可理解的,引发了很多讨论,我将尝试从中提取最有趣的部分到这篇文章中。

首先,这个错误消息与具体问题几乎没有关系。问题是——错误消息应该这样说——你不能添加列表。具体来说,在Haskell中,您只能添加实现Num typeclass的东西(列表中没有),因此您可能认为编译器会足够聪明,在这个错误消息的任何地方提到类似于“期望[a]有Num instance,但它没有”这样的内容这才是真正的问题,即使没有得到很好的解释。

但是,ghc试图假设你的意思是你所写的,并找出[a]可以拥有Num实例的方式。这就是它失败的原因,然后它给出了如何使其成功的建议。正如我的教授同事所指出的,这是一个危险的建议,尤其是对初学者来说,因为在这种情况下,使用FlexibleContexts实际上是没有帮助的。问题不在于这些列表不是特别的数字,而在于您只需要接受函数中的数字列表。问题是没有列表是(或者至少应该是)数字!但是初学者可能只是按照建议去做,试着弄清楚什么是该死的灵活环境,然后发现自己身处一个痛苦的世界,离解决实际问题已经不远了。

部分原因是1型本身。Haskell,unlikeRust,允许以任何数字类型解释像1这样的文字。鉴于Haskell(如Rust)具有返回类型多态性,它可以在类型系统中直接表达这一点:

在Rust中,这类似于impl Num。这意味着1可以是Num的任何类型。结合+要求参数为Num并匹配((+)::Num a=>;a->;a->;a) ,当我们看到1+[2,3]时,我们只是想弄清楚[2,3]是怎么回事。

如果我们没有多态文字,没有1的含义是灵活的这个概念,我们会看到一个更容易理解的错误信息。如果1的意思与(1::Integer)相同(或任意选择),我们会有一个漂亮的解释:

序曲>;(1::整数)+[2,3]<;交互式>;:4:16:错误:•无法';t在“(+)”的第二个参数中,即表达式中的“[2,3]”(1::Integer)+[2,3]中,将预期类型“Integer”与实际类型“[Integer]”匹配:它=(1::Integer)+[2,3]

或者,即使双方都没有数字,我们也同样会有更好的错误信息:

[jim@palatinate:~]$ghciGHCi,8.6.5版:http://www.haskell.org/ghc/ :? 帮助前奏>;()+[1,2]<;交互式>;:1:6:错误:•无法';t在“(+)”的第二个参数中,即表达式中的“[1,2]”中,将预期类型“()”与实际类型“[Integer]”匹配:()+[1,2]在“it”的等式中:it=()+[1,2]Prelude>;

我的外卖是什么?我不认为编译器在错误信息方面受到了足够的削弱,或者Haskell社区对初学者的关注不够。Rust作为一个通讯器会将大量能量转化为良好的错误消息,因此即使Rust也有一个特性可以添加到数组中以使+工作,它仍然有一个更好的错误消息:

错误[E0277]:无法将`[{integer};2]`添加到`{integer}`->;测验rs:2:7 | 2 | 1+[2,3]| ^没有实现`{integer}+[{integer};2]`|=help:trait`Add<;[{integer};2]>;`没有为`{integer}实现`

但我也认为1的语义太过自由,给编译器留下了一个尴尬的位置。看,奇怪的是,你可以声明[2,3]一个数字,使1+[2,3]成为一个添加两个列表的表达式:

实例编号[a],其中(+)=(<;>;)(-)=(<;>;)——呃,为什么不呢?(*)=(<;>;)否定=反向abs=id signum=const[]from integer i=take(from integer i)$repeat undefinedmain=do print$signum$1+[2,3]

一旦你将列表定义为一个数字,1就会突然变成它想要的列表。这就增加了找到正确错误信息的难度:毕竟你所要求的是可能的。

最后,这让我觉得Haskell和Javascript有着共同点,这让我很难过。强类型语言不再是强类型语言。

由discus支持的评论