haskell - 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
   |                                                                ^

所以我已经知道这里出了什么问题。 ab属于 [Int] 类型,但我需要它们的类型为 [Double] .但是,我不明白为什么编译器会说它是什么:
  • 为什么提示ba在表达式中出现在它之前并且同样是错误的?
  • 在最上面的错误中,预期的类型是 [(Double, Double)] .酷,有道理。但是实际类型怎么是[(Int, Double)] ?如果两个 a 都是如何出现的?和 b[Int] ?

  • 无论哪种情况,我认为我真正需要的是有人引导我了解类型系统最终如何生成这些错误,这样我才能更好地理解错误消息为何如此。

    最佳答案

    Why is it complaining about b when a comes before it in the expression and is equally wrong?



    ...为什么不?你自己说他们都错了。 GHC 也发现他们都错了。它告诉你他们都错了。另外,a并没有真正出现“之前”,因为在 Haskell 中没有“之前”和“之后”的真正概念。如果有的话,它是“进出”,而不是“左右”,然后是 b (下面只有 ($) )是“之前” a (在 zip(.)($) 下方)。没关系,反正。

    In the top-most error, the expected type is [(Double, Double)]. Cool, makes sense. But how come the actual type is [(Int, Double)]? How did the double emerge if both a and b are [Int]?


    sum . map _etc . zip a预计类型为 [Int] -> Double , 因为类型签名并且因为它是 ($) 的左侧.钻得更深,zip a应该是 [Int] -> [(Double, Double)] .实际上是forall b. [b] -> [(Int, b)] .使用参数类型,我们可以选择设置b ~ Int , 从而推导出 zip a实际上是 [Int] -> [(Int, Int)] (这是真的)其中 [Double] -> [(Double, Double)]是预期的,或者我们可以选择设置b ~ Double (从返回类型)并确定,实际上,zip a :: [Double] -> [(Int, Double)] (这也是真的)。两种方式都会出错。事实上,我认为 GHC 正在以第三种方式做到这一点,类似于第一种方式,但我不会详细说明。

    问题的核心是:在 Haskell 程序中,如果您知道表达式周围或其中的内容的类型,则有多种方法可以确定表达式的类型。在类型良好的程序中,所有这些推导彼此一致,而在类型错误的程序中,它们通常以多种方式不一致。 GHC 只是简单地选择其中两个,以一种希望是有意义的方式称它们为“预期的”和“实际的”,并提示他们不同意。在这里,您发现第三个推导也与“预期”推导相冲突,但 GHC 出于某种原因选择不将您的推导用于“实际类型”。选择要显示的派生并不容易,尤其是在 Haskell 中,允许所有内容影响其他所有内容的类型,尽管它肯定会更好。几年前,GHC 的一位领导人做了some work在更好的错误消息上,但它似乎有轻微的链接腐烂——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,你会发现使用 ab没用,因为 a, b :: [Int] (而 GHC 实际上在错误消息中说 a :: [Int]; b :: [Int],因为有时不清楚)。然后你想办法解决它:
    poly a b x = sum . map (\(ai, bi) -> ai * (x ** bi)) . zip (fromIntegral <$> a) $ fromIntegral <$> b
    

    一切都很好。

    关于haskell - Haskell 的类型系统是如何产生这个错误的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56486856/

    相关文章:

    haskell - 如何使用 amazonka、conduit 和 lazy bytestring 进行分块

    haskell - 违反 if-then-else 的 Haskell 缩进规则

    haskell - 在 Haskell 中从用户定义的数据类型生成随机值

    树上的haskell折叠操作

    Haskell - 类型类的实例列表

    haskell - 如何在启动时使用 'stack ghci' 导入而不是加载模块?

    caching - Haskell:部分丢弃惰性评估结果

    haskell - 在 haskell 中,GHCi 命令行上的 let x= 和 x= 之间有什么区别吗?

    模式匹配的性能

    haskell - 静态类型和惰性函数语言之间有什么关系?