尝试创建一个适用于元组的Vector
类型类我遇到了一些问题
{-# LANGUAGE TypeFamilies, FlexibleInstances #-}
class Vector v where
type Scalar v :: *
vplus :: v -> v -> v
vmult :: v -> Scalar v -> v
vdot :: v -> v -> Scalar v
instance Num a => Vector (a, a) where
type Scalar (a,a) = a
(a, b) `vplus` (c, d) = (a + c, b + d)
(a, b) `vmult` m = (a * m, b * m)
(a, b) `vdot` (c, d) = a * c + b * d
问题是我需要GHC的显式类型声明以免混淆。这当然是一个小小的不便,除了vdot
似乎根本不想工作。
res :: Int
res = (2, 3) `vdot` (5, 5)
-- error: Couldn't match expected type 'Int' with actual type 'Scalar (t0, t1)'
-- The type variables 't0', 't1' are ambiguous
如果我这样做,这个错误就会消失:
res :: Int
res = ((2, 3) :: (Int, Int)) `vdot` (5, 5)
但是现在我们已经达到了冗长代码的范畴,因此它已经不再适用了。哈斯克尔应该是美丽而简洁的;不明确的类型地狱
我原本认为GHC能够自行解析type Scalar (a, a) = a
,但即使我完全删除了实例声明,错误仍然存在。当Vector (Int, Int)
是唯一可用的实例时,它甚至会抱怨。
那么这里发生了什么?有没有办法让这个工作得很漂亮?
答案 0 :(得分:3)
在
res :: Int
res = (2, 3) `vdot` (5, 5)
没有任何强制2
和3
为Int
,甚至是相同的类型。因此,Vector (a, a)
实例可能不适用。对于所有GHC都知道你打算用Vector (Float, Double)
编写另一个实例type Scalar (Float, Double) = Int
和vdot
的完全不同的实现,res
仍然会键入check。因此,GHC告诉你,2
和3
的类型含糊不清。
听起来你真的想说:当(a, b)
与{Vector
的类型相同时,一对b
只能 成为a
的实例1}}(然后使用你写的实例)。您可以在GHC中表达如下:
instance (a ~ b, Num a) => Vector (a, b) where
type Scalar (a,b) = a
(a, b) `vplus` (c, d) = (a + c, b + d)
(a, b) `vmult` m = (a * m, b * m)
(a, b) `vdot` (c, d) = a * c + b * d
a ~ b
是一个等式约束,断言a
和b
这两种类型是相同的。
现在您的示例res
可以正常运行:
*Main> (2, 3) `vdot` (5, 5) :: Int
25
这是推理意味着类型不再含糊不清。
vdot
的类型为Vector v => v -> v -> Scalar v
。因此,要res
键入检查,我们需要找到v
(2, 3) :: v
,(5, 5) :: v
和Scalar v ~ Int
。
但是(2, 3)
的格式为(a, b)
,并且有一个头部格式为Vector (a, b)
的实例。所以,我们必须使用那个实例。 (在原始程序中,我们无法执行此步骤,因为没有足够的通用实例。)
该实例的关联类型定义告诉我们Scalar (a, b) ~ a
。但我们知道Scalar (a, b)
应该是Int
,因此我们必须a ~ Int
。
该实例的约束告诉我们a ~ b
并且应该有一个实例Num a
。因此,我们也必须b ~ Int
(确实Num Int
成立。)
所以,我们得出的结论是2 :: Int
和3 :: Int
以及(5, 5) :: v
,我们也有5 :: Int
和5 :: Int
。
现在我们已经确定了哪个类型类用于表达式中的每个重载名称(2
,3
,5
,5
和{{ 1}}),所以没有歧义,我们最终可以评估表达式。
答案 1 :(得分:1)
让我们简化这个问题:
class Vector v where
type Scalar v :: *
vdot :: v -> v -> Scalar v
...
instance Num a => Vector (a, a) where
type Scalar (a,a) = a
...
res :: Int
res = (2, 3) `vdot` (5, 5)
现在,我们有
vdot :: v -> v -> Scalar v
vdot (2,3) (5,5)
所以double应用程序必须具有此类型
(2,3) :: v
(5,5) :: v
res = vdot (2,3) (5,5) :: Scalar v
扩展对的类型:
(2,3) :: (a1, a2) ~ v for some a1, a2 in class Num
(5,5) :: (b1, b2) ~ v for some b1, b2 in class Num
res = vdot (2,3) (5,5) :: Scalar v
传递性,(a1, a2) ~ (b1, b2)
,因此a1 ~ b1
和a2 ~ b2
。
(2,3) :: (a1, a2) for some a1, a2 in class Num
(5,5) :: (a1, a2)
res = vdot (2,3) (5,5) :: Scalar (a1, a2)
我们也从注释中知道
res :: Int
因此
Scalar (a1, a2) ~ Int
但是由此无法知道a1, a2
是什么。毕竟,可以使用自定义类型:
data A1 = ...
data A2 = ...
instance Num A1 where ...
instance Num A2 where ...
instance Vector (A1, A2) where
type Scalar (A1, A2) = Int -- !!!!
请注意上一个Int
。这会导致两者
type Scalar (Int, Int) = Int
type Scalar (A1 , A2 ) = Int
无法选择a1, a2
的类型。这通常用句子来表示" 类型的家庭不一定是单射的"。
另请注意,代码中缺少instance Vector (A1, A2)
无效。 GHC必须编译你的代码,期望稍后可以在其他模块中声明这样的实例("开放世界"假设)。这几乎是需要单独编译的。