在参数化函数时无法匹配预期的类型"Int"与 Num a



我有这样的代码

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

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

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,就像你最初打算的那样。

这种代码是非惯用的。Rank2Types(及其一般形式RankNTypes(在标准Haskell代码中很少需要。因此,除非你正在用类型论做一些非常有趣的事情,否则我建议使用简短的方法。话虽如此,我想包含它只是为了表明您要进行的解释实际上是可能的,尽管使用编译器扩展。

最新更新