我有这样的代码
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 :: Int
和func3 :: Int -> String
解决此问题,但是为什么必须这样做?毕竟a
可能是Int
,并且没有其他电话期望它是Int
以外的任何东西。
答案 0 :(得分:4)
我认为您的困惑来自通用量词的工作原理。
x :: Num a => a
x = 1000
这是一个承诺。它说:“对于任何数字类型a
,您都可以给我,我可以为您提供该类型的值x
”。
func3 :: Num a => a -> String
func3 p = ...
这是一个不同的承诺。它说:“对于任何数字类型a
,您都可以给我,我可以给您一个函数,该函数接受a
并返回String
”。因此func3
必须能够像Int -> String
或Double -> String
或任何其他数字函数,例如Matrix -> String
或Vector -> 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
)。因此,除非您对类型理论做一些有趣的事情,否则我建议您使用简短,简单的方法。话虽这么说,但我想将其包括在内只是为了表明您想要的解释实际上是可能的,尽管带有编译器扩展。