2

最小示例代码:

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 qa”修复了 zqBasic 中的模棱两可的变量错误。如果没有这个量词,我会得到一个模棱两可的类型变量错误。我明白为什么我需要量词,但我不明白为什么它似乎对 changeBase 没有帮助。

谢谢

4

3 回答 3

3

使用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类型签名上的 不会将不是类的类型参数的类型变量带入范围(在实际声明中)。但是,它确实将变量带入了默认声明的范围。

无论如何,这既不是这里也不是那里。

解决大多数需要访问类型变量的问题很容易——只需创建一些不做任何事情但让您适当地操作类型的辅助函数。例如,像这样的无意义函数会假装想出一个具有 phantom 类型的术语:

zq :: (Zq q a) => a -> q
zq _ = undefined

这基本上只是为您提供对功能依赖项的直接术语级别访问,因为 fundepq对任何特定的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查看p由 的类型决定的类型b,因此通过为结果分配一个临时名称,我们可以使用它来获取我们需要的类型。

在许多情况下,您也可以在没有额外定义的情况下执行此操作,但是对于类型类,您需要避免临时多态性并确保它不允许“递归”涉及其他实例。

在相关的说明中,标准库函数asTypeOf正是针对这种类型的摆弄。

于 2011-12-02T15:11:27.470 回答
1

调用value (undefined::p)转换p为某种类型a0。从 的类型value,我们唯一能弄清楚的是它a0是一个整数类型。

该值被传递给fromIntegral,它从 转换a0b。从 的类型fromIntegral,我们唯一能弄清楚的是它a0是一个整数类型。

没有什么能决定什么是类型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)
于 2011-12-02T06:36:07.663 回答
0

两种(基本上等效?)有效的方法:

1:使用 CA 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

目标始终是能够访问实参和返回类型的类型参数,这两种方法都允许我这样做。

此外,正如 CAMcAnn 指出的那样,我关于“zqBasic”构造函数和“changeBase”之间区别的问题的答案是,即使有明确的 forall,也没有将“p”放入声明的范围内。如果有人能解释为什么会这样,我将不胜感激。

于 2011-12-02T16:37:46.050 回答