我的问题是关于类型签名。
以下代码符合:
data Vector a = Vector a a a deriving (Show)
vMult :: (Num a) => Vector a -> a -> Vector a
(Vector i j k) `vMult` m = Vector (i*m) (j*m) (k*m)
但是,我不明白为什么用以下 代替上述类型签名(第2行) 工作:
vMult :: (Num a) => Vector a -> Num -> Vector a
我的理解是,m
属于Num
类型(例如,数字8
),而且i, j, k
也是Num
,因此应该没有问题计算Vector (i*m) (j*m) (k*m)
。
请理解我的理解。
答案 0 :(得分:6)
Num a
实际上并不是一种类型 Num
是一个类型类,所以如果我说Num a
,则表示a
是数字类型,那么
vMult :: (Num a) => Vector a -> a -> Vector a
表示"只要a
是数字类型,vMult
采用a
s和a
的向量,并返回{的向量{1}} S"
这意味着a
可以像
一样工作
vMult
或
vMult :: Vector Int -> Int -> Vector Int
。
请注意,您可以通过使用单个数字类型替换原始文件中的所有vMult :: Vector Double -> Double -> Vector Double
来获取每种类型。
a
对它自己没有意义 Num
本身意味着"是数字类型",所以如果你的函数有类型签名
Num
它会读取"只要vMult :: (Num a) => Vector a -> Num -> Vector a
是数字类型,a
采用vMult
s的向量而a是数字类型并返回Vector of { {1}} S&#34。它的英语不合语法就像Haskell中的错误一样。
这就像说'#34;给我一些黄油,并且有一个尖锐的优势"而不是"给我一些黄油和一把刀"。有效的类型签名就像是说"找到你可以传播的东西(称之为k),并给我一些黄油和k。"
您也不能使用a
代替a
Num a
,因为您无法在需要名词的位置使用断言:"给我是你的钥匙圈,钥匙是用金属制成的,所以我可以给你一个新的钥匙圈"。
答案 1 :(得分:2)
乘法是在Num a
和Num a
上定义的,即同一类型的2个参数属于Num
类型类。
(*) :: (Num a) => a -> a -> a
如果您的类型签名中只有Num
,则类似于要求允许在Integer
和Float
之间进行乘法,因为两者都是属于类型类的类型Num
。这就是为什么你需要指定应该使用的类型而不仅仅是Typeclass
。
当您将a
用于Num
时,已确定Vector
应属于Vector
。为了将Num
中的每个元素与所选标量相乘,该标量不仅必须属于a
,还必须属于同一类型,即{{1}}。
答案 2 :(得分:2)
Num
不是可以在那里使用的concrete type。它是一元type constructor,必须专门使用另一种类型。用OOP术语来说,Num
不是一个类似OOP的抽象“超类”类型,而是一个必须专门用于它的工厂。
因此,尝试将Num
传递到需要kind(类型的“类型”)*
的地方就像尝试传递函数一样(:: a -> b
)其中需要一个值(:: c
)(不同之处在于前者是在类型级别完成的):
ghci> :kind Int
Int :: *
ghci> :kind Num
Num :: * -> Constraint
因此,我们可以看到Num
在类型检查时将*
应用于任何类型Constraint
会产生种类Constraint
,而=>
只能在a
之前使用Vector a -> a -> Vector a
类型签名中的{1}}(限制Constraint
中{{1}}位置的正确类型,{{1}}的含义。