使用显式量词的不明确类型

时间:2011-12-02 04:48:32

标签: haskell existential-type quantifiers

最小示例代码:

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。

由于

3 个答案:

答案 0 :(得分: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没有将范围(在实际声明中)带入不是类的类型参数的类型变量。然而, 将变量置于默认声明的范围内。

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

通过需要访问类型变量来解决大多数问题很容易 - 只需创建一些不做任何事情的辅助函数,但让你适当地操作类型。例如,像这样的无意义函数会伪装成一个带有幻像类型的术语:

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才能看到p类型,该类型由b的类型决定,因此通过为结果指定临时名称,我们可以使用它来获取我们需要的类型。

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

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

答案 1 :(得分:1)

来电value (undefined::p)p转换为某种类型a0。从value的类型来看,我们唯一可以理解的是a0是一个整数类型。

该值传递给fromIntegrala0b转换为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) 不会引用函数类型签名及其正文中的相同变量。

以下代码适用于我:

{{1}}

答案 2 :(得分:0)

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

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指出的那样,'p'没有被置于声明的范围内,即使有明确的forall。如果有人能够解释为什么,我会很感激。