最小示例代码:
class IntegralAsType a where
value :: (Integral b) => a -> b
class (Num a, Fractional a, IntegralAsType q) => Zq q a | a -> q where
changeBase:: forall p b . (Zq q a, Zq p b) => a -> b
newtype (IntegralAsType q, Integral a) => ZqBasic q a = ZqBasic a deriving (Eq)
zqBasic :: forall q a . (IntegralAsType q, Integral a) => a -> ZqBasic q a
zqBasic x = ZqBasic (mod x (value (undefined::q)))
instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
changeBase (ZqBasic x) = fromIntegral (value (undefined::p)) -- ZqBasic is an instance of Num
这里有一点关于我试图完成的背景:IntegralAsType通过防止像两个具有不同模数的数字相加这样的事情来确保编译时的类型安全。ZqBasic是Zq类型的内部表示,还有其他类型,这就是为什么Zq是这样定义的。我们的目标是得到一个对内部表示透明的系统。
我的问题是与changeBase函数。我在"p"类型上使用显式forall,但我仍然得到一个"模糊类型变量a0在约束(IntegralAsType a0)中产生的使用值"
我很困惑为什么我得到这个错误。特别是在之前的一篇文章中,我得到了类似"zqBasic"函数的帮助,它似乎与changeBase函数具有相同的设置。我通过添加显式量词"forall q a"修复了zqBasic中的模糊变量错误。没有这个量词,我得到一个模糊的类型变量错误。我明白为什么我需要量词,但我不明白为什么它对changeBase没有帮助。
谢谢
使用ScopedTypeVariables
在这里没有帮助,因为您正在使用的p
似乎不在范围内。比较以下定义:
changeBase (ZqBasic x) = fromIntegral (value (undefined::foobar))
这将给出相同的错误,因为它也创建了一个新的类型变量。
changeBase (ZqBasic x) = fromIntegral (value (5::p))
然而,会给出不同的错误。相关位为:
Could not deduce (Num p1) arising from the literal `5'
(snip)
or from (Zq q (ZqBasic q a), Zq p b)
bound by the type signature for
changeBase :: (Zq q (ZqBasic q a), Zq p b) => ZqBasic q a -> b
这表明p
被实例化为一个新的类型变量。我猜测类型签名上的forall
没有将不是类的类型参数的类型变量带入作用域(在实际声明中)。但是,如果将变量带入默认声明的作用域。
不管怎样,那都是无关紧要的。
解决需要访问类型变量的大多数问题是很容易的——只需创建一些辅助函数,这些函数不做任何事情,只是让您适当地操作类型。例如,像这样的无意义函数将假装召唤出具有虚幻类型的术语:
zq :: (Zq q a) => a -> q
zq _ = undefined
这基本上只是给你直接的术语级访问功能依赖,因为基金使q
对任何特定的a
明确。当然,你不能得到一个实际的值,但这不是重点。如果undefined
让您感到困扰,请使用[] :: [q]
来达到类似的效果,并且只在需要时使用head
或其他内容。
现在您可以使用where
子句或诸如此类的东西来强制推断正确的类型:
instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
changeBase (ZqBasic x) = b
where b = fromIntegral (value (zq b))
这里发生的事情是,b
是我们真正想要的东西,但是我们需要value
来看到由b
的类型决定的p
类型,所以通过给结果赋一个临时名称,我们可以使用它来获得我们需要的类型。
在许多情况下,您也可以在没有额外定义的情况下做到这一点,但是对于类型类,您需要避免特殊多态性,并确保它不允许"递归"涉及其他实例。
与此相关的是,标准库函数asTypeOf
正是用于这种类型的处理。
调用value (undefined::p)
从p
转换为某种类型的a0
。从value
的类型,我们唯一能得出的是a0
是一个整型。
传递给fromIntegral
,由a0
转换为b
。从fromIntegral
的类型,我们唯一能得出的是a0
是一个整型。
Nothing决定a0
是什么类型,因此需要在表达式value (undefined::p)
上使用类型注释来解决歧义。从类型签名来看,看起来value
应该能够生成正确的返回类型,而无需进行任何额外的转换。您可以简单地删除对fromIntegral
的调用吗?
编辑
需要启用ScopedTypeVariables
扩展。如果没有ScopedTypeVariables
,一个类型变量不能在多个类型签名中被提及。在本例中,变量名p
在函数的类型签名和函数体中并没有引用相同的变量。
下面的代码为我工作:
instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
changeBase = zqBasicChangeBase
zqBasicChangeBase :: forall p b q a.
(IntegralAsType q, IntegralAsType p, Integral a, Zq p b) => ZqBasic q a -> b
zqBasicChangeBase (ZqBasic x) = fromIntegral (value (undefined :: p) :: Integer)
两种(本质上等同的?)可行的方法:
1:使用C.A. McCann的方法:
instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
changeBase (ZqBasic x) = b
where b = fromIntegral ((value (zq b))*(fromIntegral (value (zq q)))) -- note that 'q' IS in scope
2:我没有使用签名a->b,而是将changeBase的签名更改为b->a下面的代码可以工作:
instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
changeBase x = fromIntegral ((value (zq x))*(fromIntegral (value (zq q)))) -- note that 'q' IS in scope
我们的目标始终是能够访问实参和返回类型的类型形参,这两种方法都允许我这样做。
同样,我关于"zqBasic"构造函数和"changeBase"构造函数之间的区别的问题的答案是,正如C.A.McAnn所指出的,"p"在声明中没有放入作用域,即使有显式的forall。如果有人能解释为什么是这样,我将不胜感激。