Haskell:Double 和 Int 之间的变量类型混淆

Fie*_*Foe 20 haskell types

作为一个 uni 赋值,我应该编写一个带有类型声明的函数:

pi_approx :: Int -> Double
Run Code Online (Sandbox Code Playgroud)

这是我的第一次尝试:

pi_approx :: Int -> Double
pi_approx x = let total = sum [1 / (y^2) | y <- [1..x]]
    in sqrt (6 * total)
Run Code Online (Sandbox Code Playgroud)

这引发了以下错误:

pi_approx.hs:4:8: error:
    * Couldn't match expected type `Double' with actual type `Int'  
    * In the expression: sqrt (6 * total)
      In the expression:
        let total = sum [1 / (y ^ 2) | y <- ...] in sqrt (6 * total)
      In an equation for `pi_approx':
          pi_approx x = let total = sum ... in sqrt (6 * total)
  |
4 |     in sqrt (6 * total)
  |        ^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

我一步一步地试图理解为什么解释器把它当作一个 Int:

level1 :: Fractional a => a -> a
level1 x = 1 / (x^2)
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好。

level2 :: (Enum a, Fractional a) => a -> [a]
level2 x = [level1 y | y <- [1..x]]

Run Code Online (Sandbox Code Playgroud)

也符合预期。

level3 :: (Enum a, Fractional a) => a -> a
level3 x = sum (level2 x)
Run Code Online (Sandbox Code Playgroud)

没有 Int 被看到...

level4 :: (Enum a, Fractional a) => a -> a
level4 x = 6 * (level3 x)
Run Code Online (Sandbox Code Playgroud)

最后

level5 :: (Floating a, Enum a) => a -> a
level5 x = sqrt (level4 x)
Run Code Online (Sandbox Code Playgroud)

进入这个兔子洞后,我离我的答案更近了。我让它运行fromIntegral

pi_approx :: Int -> Double
pi_approx x = let total = sum [(fromIntegral 1) / ((fromIntegral y)^ (fromIntegral 2)) | y <- [1..x]]
    in sqrt (6*total)
Run Code Online (Sandbox Code Playgroud)

但这让我无法理解导致第一个版本错误的原因。有人可以解释我缺少什么吗?为什么被sqrt (6*total)当作Int?

luq*_*qui 17

由于类型类的类型检查方式,您看到的类型错误并不是最有用的。

[ 1 / (y^2) | y <- [1..x] ]
Run Code Online (Sandbox Code Playgroud)

因此,这可能不足为奇,因为xInt[1..x]Ints的列表,所以yInt。也不是令人惊讶的y^2Int。GHC 失去我们的地方是当它决定因此1 / (y^2)是一个Int.

原因是这样的:除法运算符的类型是

(/) :: (Fractional a) => a -> a -> a
Run Code Online (Sandbox Code Playgroud)

也就是说,它在两边取相同的类型,并返回它。因此,一旦y^2知道的类型是Int,我们就推断出1并且整个表达式Int也是。 只有稍后,在约束检查阶段,GHC 才会确定IntFractional,当然不是。例如:

recp :: Int -> Int
recp x = 1 / x
Run Code Online (Sandbox Code Playgroud)

会给你一个更好的错误

• No instance for (Fractional Int) arising from a use of ‘/’
• In the expression: 1 / x
  In an equation for ‘recp’: recp x = 1 / x
Run Code Online (Sandbox Code Playgroud)

但是在检查您的函数时并没有走那么远,因为首先出现了类型统一失败。

有时,如果您无法找出错误,删除签名并查看推断出的类型会有所帮助(更多情况下,添加更多类型签名会很有帮助,但并非总是如此)

ghci> :t pi_approx
pi_approx :: (Floating a, Enum a) => a -> a
Run Code Online (Sandbox Code Playgroud)

如果没有签名,该函数实际上会进行类型检查。它也有效:

ghci> pi_approx 100
3.1320765318091053
Run Code Online (Sandbox Code Playgroud)

这里100默认为 aDouble来满足Fractional约束,并且整个定义将所有内容都作为Doubles。

只是它不接受Ints:

ghci> pi_approx (100 :: Int)

<interactive>:1:1: error:
• No instance for (Floating Int) arising from a use of ‘pi_approx’
• In the expression: pi_approx (1 :: Int)
Run Code Online (Sandbox Code Playgroud)