【问题标题】:Ambiguous type using explicit quantifiers使用显式量词的模糊类型
【发布时间】:2011-12-02 04:48:31
【问题描述】:

最小示例代码:

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,但我仍然得到一个“由于使用值而导致的约束(IntegralAsType a0)中的模糊类型变量a0”

我对为什么会收到此错误感到困惑。特别是在上一篇文章中,我得到了类似“zqBasic”函数的帮助,它似乎与 changeBase 函数具有相同的设置。我通过添加显式量词“forall q a”修复了 zqBasic 中的模棱两可的变量错误。如果没有这个量词,我会得到一个模棱两可的类型变量错误。我明白为什么我需要量词,但我不明白为什么它似乎对 changeBase 没有帮助。

谢谢

【问题讨论】:

  • 额外说明:我正在使用 ScopedTypeVariables、FlexibleInstances、MultiParamTypeClasses 和 FunctionalDependencies 扩展。当 (undefined::p) 被 'changeBase' 中的 (undefined::q) 替换时,上面的代码有效

标签: haskell existential-type quantifiers


【解决方案1】:

在这里使用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

这基本上只是为您提供对函数依赖项的直接术语级访问,因为fundep 使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 才能看到类型 pb 的类型决定,所以通过分配一个临时名称结果我们可以使用它来获得我们需要的类型。

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

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

【讨论】:

  • 为了清楚一点,上面的代码中有两个'b':前两个是局部变量,第三个是指签名中的类型,对吗?
  • @Eric:在最终的代码sn-p中,b的三个用法都指的是同一个东西;它根本没有提到类型。事实上,避免引用类型是我们的目标。
【解决方案2】:

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

该值被传递给fromIntegral,它从a0 转换为b。从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)

【讨论】:

  • 感谢您的明确解释,但这似乎不起作用。不应该 fromIntegral 使用任何(任意)整数值并将其转换为“正确”类型吗?我认为,根据返回类型的上下文,应该推断出对 fromIntegral 的调用的正确类型。另外,对于它的价值,如果我使用“(值(undefined :: q))”而不是undefined :: p,代码可以正常工作,所以问题似乎是因为p不受参数约束?跨度>
  • 我也尝试过明确说明对value的调用类型,即((value (undefined::p))::Integer),但是得到同样的错误,所以问题不似乎与价值的模棱两可的类型。
  • 您需要 ScopedTypeVariables 扩展名来表示您想要的类型签名。它在打开该扩展程序的情况下为我编译。
  • 非常有趣,因为我已经在使用 ScopedTypeVariables。我正在使用 GHCi 7.0.3
  • 在编译您的代码时,我做的第一件事就是将changeBase 的定义移动到一个单独的函数中并编写它的类型签名。类定义中引入的类型变量不在类实例的范围内。
【解决方案3】:

两种(本质上等价的?)可行的方法:

1:使用 C.A.麦肯的做法:

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 所指出的那样,即使有明确的 forall,也没有将“p”放入声明的范围内。如果有人能解释为什么会这样,我将不胜感激。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-22
    • 1970-01-01
    • 1970-01-01
    • 2015-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多