我有这样的代码
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
。
我认为您的困惑源于通用量词的工作原理。
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
,就像你最初打算的那样。
这种代码是非惯用的。Rank2Types
(及其一般形式RankNTypes
(在标准Haskell代码中很少需要。因此,除非你正在用类型论做一些非常有趣的事情,否则我建议使用简短的方法。话虽如此,我想包含它只是为了表明您要进行的解释实际上是可能的,尽管使用编译器扩展。