为什么Haskell类型系统无法捕获这个?

时间:2014-02-14 10:03:29

标签: haskell compiler-errors typechecking

以下编译时没有警告或错误。

factors n = [x | x<-[1..n], n `mod` x == 0]
perfects n = [x | x <- [1..n], x == sum (factors (init x))]
main = putStrLn "hello"

即使我犯了错误。

perfects n = [x | x <- [1..n], x == sum (factors (init x))]  -- incorrect
perfects n = [x | x <- [1..n], x == sum (init (factors x))]  -- should have been

静态类型检查救援在哪里?

我认为应该抓住错误的原因是:

  • factor显然需要Integral,因为其参数与mod一起使用,而init则返回List
  • 更不用说x来自List Integersinit期待List

2 个答案:

答案 0 :(得分:18)

如果你看一下ghc推断出的类型,你可以看到

perfects :: forall a. (Eq a, Integral [a]) => [a] -> [[a]]

因此,如果您有一个实例使列表位于类Integral中,则它可以正常工作。 而ghc并不知道这不是你想要的。

如果您将预期的类型签名放在perfects上,或者您按照预期的方式使用perfects,则会收到错误(将main更改为{{1} }})。

编辑这会让你的代码做一些事情(荒谬):

print (perfects 42)

所以你写的可能是你想要的。这就是为什么总是编写类型签名是好的,这样编译器就可以看到你的想法了。或者至少,您应该检查推断类型是否符合您的意图。

答案 1 :(得分:5)

奥古斯都说了什么,但我会补充一些想法。

这里的问题(没问题!)是Haskell做出权衡:更具灵活性,代价是具体的错误报告。由于Haskell只允许更多的东西,因此在很多情况下,与更主流的语言相比,它要么无法报告错误,要么报告的错误比其他语言报告的更加抽象。

例如,假设您打算键入1 + 2,但是您手指并输入1 0 2。以下是Python的响应方式:

Python 2.7.2 (default, Oct 11 2012, 20:14:37) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 1 0 2
  File "<stdin>", line 1
    1 0 2
      ^
SyntaxError: invalid syntax

简单:“你输错了。”现在哈斯克尔:

GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> 1 0 2

<interactive>:2:1:
    No instance for (Num (a0 -> a1 -> t0)) arising from the literal `1'
    Possible fix:
      add an instance declaration for (Num (a0 -> a1 -> t0))
    In the expression: 1
    In the expression: 1 0 2
    In an equation for `it': it = 1 0 2

<interactive>:2:3:
    No instance for (Num a0) arising from the literal `0'
    The type variable `a0' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Note: there are several potential instances:
      instance Num Double -- Defined in `GHC.Float'
      instance Num Float -- Defined in `GHC.Float'
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      ...plus three others
    In the first argument of `1', namely `0'
    In the expression: 1 0 2
    In an equation for `it': it = 1 0 2

<interactive>:2:5:
    No instance for (Num a1) arising from the literal `2'
    The type variable `a1' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Note: there are several potential instances:
      instance Num Double -- Defined in `GHC.Float'
      instance Num Float -- Defined in `GHC.Float'
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      ...plus three others
    In the second argument of `1', namely `2'
    In the expression: 1 0 2
    In an equation for `it': it = 1 0 2

在Python中,1 0 2是语法错误。在Haskell中,1 0 2表示将函数1应用于参数02。 Haskell的错误消息不是“你不能这样做”,而是“你没有告诉我如何将数字强制转换为双参数函数”(没有Num (a0 -> a1 -> t0)的实例)。

在你的情况下,你设法编写了Haskell知道如何解释的东西,但意味着与你的意思截然不同的东西。作为程序员,这里最好的做法是使用描述你的意图的顶级类型声明,然后编译器可以检查这些。


最后注意事项:请记住,您可以在Haskell中执行此操作:

-- | Treat lists of numbers as numbers.  Example:
--
-- >>> [1..3] * [2..5]
-- [2,3,4,5,4,6,8,10,6,9,12,15]
--
instance Num a => Num [a] where
    xs + ys = [x + y | x <- xs, y <- ys]
    xs * ys = [x * y | x <- xs, y <- ys]
    xs - ys = [x - y | x <- xs, y <- ys]
    negate xs = map negate xs
    abs xs = map abs xs
    signum xs = map signum xs
    fromInteger x = [fromInteger x]



-- | Treat functions as numbers if they return numbers.  The functions
-- must have the same argument type.  Example:
--
-- >>> 1 0 2
-- 1
instance Num a => Num (r -> a) where
    f + g = \r -> f r + g r
    f * g = \r -> f r * g r
    f - g = \r -> f r - g r
    negate f = negate . f
    abs f = abs . f
    signum f = signum . f
    fromInteger x = const (fromInteger x)

使用Integral类可以完成同样的事情。