haskell - Haskell 的类型系统是如何产生这个错误的?
问题描述
在我通过 Haskell 进行的冒险中,我发现当我在代码中的类型出现错误时,我很难解析我做错了什么并且编译器正在抱怨。我认为这是因为在编译器发现错误之前进行了部分类型推断。
当然,我习惯了类型不匹配非常明显的语言。例如类似的东西function foo expects an argument of type int, but received string
。很明显这意味着什么,我传入了一个字符串,但签名需要一个 int。
所以这里有一些相对简单的代码,它是一个在给定系数和幂列表的情况下评估多项式的函数:
poly :: [Int] -> [Int] -> Double -> Double
poly a b x = sum . map (\(ai, bi) -> ai * (x ** bi)) . zip a $ b
这会产生以下编译器错误输出:
[1 of 1] Compiling Main ( solution.hs, solution.o )
solution.hs:11:56: error:
• Couldn't match type ‘Int’ with ‘Double’
Expected type: [Double] -> [(Double, Double)]
Actual type: [Double] -> [(Int, Double)]
• In the second argument of ‘(.)’, namely ‘zip a’
In the second argument of ‘(.)’, namely
‘map (\ (ai, bi) -> ai * (x ** bi)) . zip a’
In the expression: sum . map (\ (ai, bi) -> ai * (x ** bi)) . zip a
|
11 | poly a b x = sum . map (\(ai, bi) -> ai * (x ** bi)) . zip a $ b
| ^^^^^
solution.hs:11:64: error:
• Couldn't match type ‘Int’ with ‘Double’
Expected type: [Double]
Actual type: [Int]
• In the second argument of ‘($)’, namely ‘b’
In the expression:
sum . map (\ (ai, bi) -> ai * (x ** bi)) . zip a $ b
In an equation for ‘poly’:
poly a b x = sum . map (\ (ai, bi) -> ai * (x ** bi)) . zip a $ b
|
11 | poly a b x = sum . map (\(ai, bi) -> ai * (x ** bi)) . zip a $ b
| ^
所以我已经知道这里出了什么问题。a
并且b
是类型的[Int]
,但我需要它们是类型的[Double]
。但是,我不明白为什么编译器会说它是什么:
- 为什么它在表达式中抱怨
b
当它出现在它之前并且同样是错误的?a
- 在最顶层的错误中,预期的类型是
[(Double, Double)]
. 酷,有道理。但是实际类型是怎么来的[(Int, Double)]
?如果两者都是a
和,那么双重是如何出现b
的[Int]
?
无论哪种情况,我认为我真正需要的是有人引导我了解类型系统最终如何生成这些错误,这样我才能更好地理解错误消息为何如此。
解决方案
为什么它在表达式中抱怨
b
当它出现在它之前并且同样是错误的?a
...为什么不?你自己说他们都错了。GHC 也发现他们都错了。它告诉你他们都错了。此外,a
并不是真的出现在“之前”,因为在 Haskell 中没有真正的“之前”和“之后”的概念。如果有的话,它是“in-out”,而不是“left-right”,然后b
(仅在 a 下方)是(underneath 、和)的($)
“之前” 。没关系,反正。a
zip
(.)
($)
在最顶层的错误中,预期的类型是
[(Double, Double)]
. 酷,有道理。但是实际类型是怎么来的[(Int, Double)]
?如果两者都是a
和,那么双重是如何出现b
的[Int]
?
sum . map _etc . zip a
预计会有 type [Int] -> Double
,因为类型签名并且因为它是 a 的左侧($)
。钻得更深,zip a
应该是[Int] -> [(Double, Double)]
。它实际上是forall b. [b] -> [(Int, b)]
。使用参数类型,我们可以选择 set b ~ Int
,从而推断出zip a
实际是[Int] -> [(Int, Int)]
(这是真的) a[Double] -> [(Double, Double)]
是预期的,或者我们可以选择设置b ~ Double
(从返回类型)并决定,实际上,zip a :: [Double] -> [(Int, Double)]
(这也是真的) . 两种方式都会出错。事实上,我认为 GHC 正在以第三种方式做到这一点,类似于第一种方式,但我不会详细说明。
问题的核心是:在 Haskell 程序中,如果您知道表达式周围或其中的内容的类型,则有多种方法可以确定表达式的类型。在类型良好的程序中,所有这些推导彼此一致,而在类型错误的程序中,它们通常以多种方式不一致。GHC 只是选择了其中两个,以一种希望是有意义的方式称它们为“预期的”和“实际的”,并抱怨他们不同意。在这里,您发现第三个推导也与“预期”推导相冲突,但 GHC 出于某种原因,选择不将您的推导用于“实际类型”。选择要显示的派生并不容易,尤其是在 Haskell 中,允许一切都影响其他一切的类型,尽管它肯定会更好。几年前,一些工作在更好的错误消息上,但它似乎有轻微的链接腐烂——Haskell-analyzer 桥似乎已经链接腐烂了互联网。
如果你遇到这样的错误,我建议首先不要以这种_ . _ . ... $ _
风格写作。如果你把它写成_ $ _ $ ... $ _
. 我不会在这里改变它,但你应该记住这一点。
poly :: [Int] -> [Int] -> Double -> Double
poly a b x = sum . map (\(ai, bi) -> ai * (x ** bi)) . zip a $ b
您会看到一个错误,但要更改的内容并不是很明显。很好,干脆放弃尝试破译象形文字并将 RHS 的部分替换为_
. 您删除的 RHS 越多,发现错误的机会就越大,如果您将其全部删除,则会达到约 95%:
poly a b x = sum . map (\(ai, bi) -> ai * (x ** bi)) . _ $ _
-- personally, I'd nuke the scary-looking map _lambda, too,
-- but I'm also trying to keep this short
GHC 会告诉你,左边_
是注定的_a -> [(Double, Double)]
,右边是_a
。添加zip
:
poly a b x = sum . map (\(ai, bi) -> ai * (x ** bi)) . zip _ $ _
并且您会被告知您需要两个[Double]
s,并且您会意识到 using a
and b
didn't work because a, b :: [Int]
(GHC 实际上a :: [Int]; b :: [Int]
在错误消息中说,因为有时不清楚)。然后你想办法解决它:
poly a b x = sum . map (\(ai, bi) -> ai * (x ** bi)) . zip (fromIntegral <$> a) $ fromIntegral <$> b
一切都很好。
推荐阅读
- angular - 如何在 Angular 中使用“when”关键字
- java - Spring RabbitListener 在发送消息后调用方法
- python-3.x - PyParsing OnlyOnce
- java - 如何编写支持嵌套元组集合的java实体类?
- c# - 在 Blazor 中选择框绑定
- python - 使用 excel 表作为散景图的来源
- android - 修改ionic项目中的AndroidManifest.xml
- c# - Minesweeper:我想将我的地雷设置为二维数组。我怎样才能检查它?
- angular - 多个 Angular 组件的一个可重用动画?
- javascript - 如何获得第二个定义的单选按钮值?