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

use*_*706 1 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"
Run Code Online (Sandbox Code Playgroud)

即使我犯了错误.

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
Run Code Online (Sandbox Code Playgroud)

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

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

  • factor显然期望一个Integral因为它的参数使用mod,而init返回aList
  • 更何况x从绘制ListIntegers,并init预计一List

aug*_*tss 18

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

perfects :: forall a. (Eq a, Integral [a]) => [a] -> [[a]]
Run Code Online (Sandbox Code Playgroud)

所以如果你有一个实例让列表在课堂上Integral就可以了.并且ghc不知道那不是你想要的.

如果您打开了预期的类型签名perfects,或者您perfects按照预期的方式使用(更改mainprint (perfects 42)),则会出现错误.

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

module Main where
factors n = [x | x<-[1..n], n `mod` x == 0]
perfects n = [x | x <- [1..n], x == sum (factors (init x))]

instance Num [a]
instance Integral a => Integral [a]
instance Real a => Real [a]
instance Enum [a] where
    enumFromTo _ _ = []

main = print (perfects 5)
Run Code Online (Sandbox Code Playgroud)

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

  • 不,这完全是奥古斯丁指出的原因并不是错误的.如果你真的尝试使用`perfects`或者如果你在`perfects`上放一个正确的类型签名那么你会得到一个类型错误. (6认同)

Lui*_*las 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
Run Code Online (Sandbox Code Playgroud)

简单:"你打错了." 现在哈斯克尔:

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
Run Code Online (Sandbox Code Playgroud)

在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)
Run Code Online (Sandbox Code Playgroud)

同样的事情可以在Integral课堂上完成.