使用Num a参数化功能时,无法匹配预期的类型“ Int”

时间:2018-06-26 13:28:07

标签: haskell typeclass

我有这样的代码

main :: IO ()
main = putStrLn (show func1)

x,y :: Num a => a
x = 1000
y = 1000

func1 :: String
func1 = func3  ++ "!"

func3 :: String
func3 = show (1 + sum' x y)

sum' :: Int -> Int -> Int
sum' a b = a+b

,我想将x用作参数。所以我想我会像这样重写它:

func1 :: String
func1 = func3 x ++ "!"

func3 :: Num a => a -> String
func3 p = show (1 + sum' p y)

我希望这没问题,因为我没有修改任何约束,但是我得到了

file:21:27: error:
    • Couldn't match expected type ‘Int’ with actual type ‘a’
      ‘a’ is a rigid type variable bound by
        the type signature for:
          func3 :: forall a. Num a => a -> String
        at file.hs:20:1-29
    • In the first argument of ‘sum'’, namely ‘p’
      In the second argument of ‘(+)’, namely ‘sum' p y’
      In the first argument of ‘show’, namely
        ‘(1 + sum' p y)’
    • Relevant bindings include
        p :: a (bound at file.hs:21:7)
        func3 :: a -> String (bound at file.hs:21:1)
   |
21 | func3 p = show (1 + sum' p y)
   |      

我可以使用x :: Intfunc3 :: Int -> String解决此问题,但是为什么必须这样做?毕竟a可能是Int,并且没有其他电话期望它是Int以外的任何东西。

1 个答案:

答案 0 :(得分:4)

我认为您的困惑来自通用量词的工作原理。

x :: Num a => a
x = 1000

这是一个承诺。它说:“对于任何数字类型a,您都可以给我,我可以为您提供该类型的值x”。

func3 :: Num a => a -> String
func3 p = ...

这是一个不同的承诺。它说:“对于任何数字类型a,您都可以给我,我可以给您一个函数,该函数接受a并返回String”。因此func3必须能够像Int -> StringDouble -> String或任何其他数字函数,例如Matrix -> StringVector -> String一样起作用。因此,您不能假设参数为Int

轻松修复

sum'仅使用+,因此它也应该可以采用任何数字类型。只需将参数更改为sum',您的func3就可以开始工作。

sum' :: Num a => a -> a -> a
sum' x y = x + y

使用上面的类比,这里的类型签名可以保证:“如果给我任何数字类型a,我可以创建一个函数,该函数需要两个a,将它们加在一起,然后返回a”。

这可能是您想要的修复程序。 99%的时间,这将是更好的解决方案。另外,您可以更改func3,使其仅使用Int,然后您将具有有效的功能(尽管不那么抽象)。

有时,如果您认为函数可以具有更通用的类型,但不确定,可以注释掉类型签名并询问GHCi推断的签名是什么。 GHCi用法示例:

> let sum' x y = x + y
> :t sum'
sum' :: Num a => a -> a -> a

因此,解释器为您提供了sum'的最通用类型,然后您可以将其放入代码中以使其更加通用和抽象。

长答案

让我们再来看一下您的代码。

y :: Num a => a
y = 1000

func3 :: Num a => a -> String
func3 p = show (1 + sum' p y)

sum' :: Int -> Int -> Int
sum' x y = x + y

现在,签名Num a => a -> String这样关联:Num a => (a -> String)。根据您的评论,我相信您将其解释为(Num a => a) -> String,这意味着“此函数接受一个可以解释为任何数字类型的参数并返回字符串”。现在,这实际上不是有效的Haskell代码。但是,如果您正在使用GHC(可能是这样),则可以启用编译器扩展Rank2Types来获得此行为。

{-# LANGUAGE Rank2Types #-}

y :: Num a => a
y = 1000

func3 :: (forall a. Num a => a) -> String
func3 p = show (1 + sum' p y)

sum' :: Int -> Int -> Int
sum' x y = x + y

参数类型开头的forall本质上说:“是的,我知道我在做的不是通常的Haskell解释,而是无论如何都要这样做”。现在func3实际上是有希望的:“给我一个可以解释为任何数字类型a的参数,我将输出一个字符串”。因此,当func3调用sum'时,它可以像您最初打算的那样简单地将自变量解释为Int

这种代码不是惯用的。标准的Haskell代码中很少需要Rank2Types(及其一般形式RankNTypes)。因此,除非您对类型理论做一些有趣的事情,否则我建议您使用简短,简单的方法。话虽这么说,但我想将其包括在内只是为了表明您想要的解释实际上是可能的,尽管带有编译器扩展。