在Haskell中,即使启用AllowAmbiguousTypes,为什么类型仍然模棱两可?

果酱

因此,假设您有两个这样定义的类型类:

{-# LANGUAGE MultiParamTypeClasses #-}

class F a c where f :: a -> c
class G c b where g :: c -> b

然后,您想使用f和g以一般方式定义新函数h。

h a = g (f a)

我们知道此函数具有类型,a -> b因此c在其中隐含。我想把它留给的实现者f以及可能是g什么cHaskell抱怨这句话c模棱两可。

然后按照错误提示,我打开了这个扩展名:

{-# LANGUAGE AllowAmbiguousTypes #-}

现在可以了!真好

我相信通常作为一种好的软件工程实践,我想写出我的函数的明确规范,以告诉编译器我期望我的函数应表现为什么样。这样以后的编译器就可以抱怨我不尊重我的设置。

因此,我想在其之前添加函数的类型:

h :: (F a c, G c b) => a -> b
h a = g (f a)

现在,类型歧义错误再次出现...为什么?

总结一下为什么Haskell在下面的这段代码中会抱怨呢?即使显式启用了AllowAmbiguousTypes。如何在保留显式函数类型定义的同时进行修复?我知道删除该函数的类型定义可以解决该问题,但是我不想对事物进行低估。

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}

class F a c where f :: a -> c
class G c b where g :: c -> b

h :: (F a c, G c b) => a -> b
h a = g (f a)

Haskell为什么抱怨呢?

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}

class F a c where f :: a -> c
class G c b where g :: c -> b

h a = g (f a)

错误讯息:

error:
    * Could not deduce (G c0 b) arising from a use of `g'
      from the context: (F a c, G c b)
        bound by the type signature for:
                   h :: forall a c b. (F a c, G c b) => a -> b

      The type variable `c0' is ambiguous
      Relevant bindings include
        h :: a -> b 
    * In the expression: g (f a)
      In an equation for `h': h a = g (f a)
    |
    | h a = g (f a)
    |       ^^^^^^^
error:
    * Could not deduce (F a c0) arising from a use of `f'
      from the context: (F a c, G c b)
        bound by the type signature for:
                   h :: forall a c b. (F a c, G c b) => a -> b
      The type variable `c0' is ambiguous
      Relevant bindings include
        a :: a 
        h :: a -> b 
    * In the first argument of `g', namely `(f a)'
      In the expression: g (f a)
      In an equation for `h': h a = g (f a)
    |
    | h a = g (f a)
    |          ^^^
开支

您的代码含糊不清,以至于编译器无法自动解决。假设:

class F a c where f :: a -> c
class G c b where g :: c -> b

instance F Int String where f = show
instance G String Bool where g = null

h :: (F Int c, G c Bool) => Int -> Bool
h a = g (f a)

现在,最后一行使用了哪些实例?我们有两个选择:使用上下文提供的实例(F Int c, G c Bool),或者忽略该上下文,并使用上述实例String作为中间类型。两种解释都是正确的,实际上我们可以明确地写

h1 :: forall c. (F Int c, G c Bool) => Int -> Bool
h1 a = (g :: c -> Bool) (f a)

h2 :: forall c. (F Int c, G c Bool) => Int -> Bool
h2 a = (g :: String -> Bool) (f a)

选择一种或另一种方式。GHC无法以合理的方式为我们做出选择。可以根据一些启发式方法选择一个,但是这可能会给程序员带来很多惊喜。因此,我们可以争辩说,GHC绝对不能选择,不要报告歧义,而应该让程序员阐明其意图。

最后,请注意,您的代码不包含上述两个实例这一事实是不相关的,因为可以在以后甚至在另一个模块中添加它们,因此GHC必须保持保守,并避免假定它们将永远不存在。

Haskell为什么不抱怨呢?

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}

class F a c where f :: a -> c
class G c b where g :: c -> b

h x = g (f x)  -- [renamed to x for clarity]

好点子。在这里,GHC可以找到最通用的类​​型,即

h :: forall a b c. (F a c, G c b) => a -> b
h x = (g :: c -> b) ((f :: a -> b) x)

由于这里是GHC添加了类型变量c,因此GHC可以确保该类型为中间类型。毕竟,在类型推断期间创建了该类型变量以表示中间类型。

而是,当用户显式编写上下文时,GHC无法猜测用户的意图。即使实际上不太可能,用户也可能不想使用该实例,而是想要使用另一个实例(在程序中可用,而在上下文中不存在)。

考虑这种情况也可能会有所帮助:

data T = ....

h :: forall a b c. (F a c, G c b, F a T, G T b) => a -> b
h x = g (f x)

我认为您可以同意应该拒绝此代码:中间类型可以是Tc,并且没有理智的方法来解决它。现在考虑这种情况:

h :: forall a b c. (F a c, G c b) => a -> b
h x = g (f x)

instance F a T where ...
instance G T b where ...

现在,这与之前的情况没有太大不同。我们没有在上下文中有两个选项,而是将一个选项移到了外面。但是,GHC仍然有两个选择。因此,同样,理智的事情是拒绝代码,并要求程序员提供更多细节。


在GHCi中,一个更简单的方案是:

> :set -XScopedTypeVariables
> :set -XAllowAmbiguousTypes
> class C a where c :: String
> instance C Int where c = "Int"
> instance C Bool where c = "Bool"
> let g :: forall a. C a => String ; g = c

<interactive>:7:40: error:
    * Could not deduce (C a0) arising from a use of `c'

在这里,GHC怎么会知道,当我写的时候,g = c我的意思是“c来自上下文来临C a?”我可以写出“c来自实例的for Int”的意思Bool

GHC在内部生成一个新的类型变量a0,然后尝试解决约束C a0它有三种选择:选择a0 = aa0 = Inta0 = Bool(以后可以添加更多实例!)

因此,它是模棱两可的,并且没有理智的方法可以在不猜测程序员意图的情况下对其进行修复。唯一安全的选择是拒绝。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

Haskell为什么说这是模棱两可的?

为什么成员函数称为“模棱两可”?

为什么此方法重载模棱两可?

为什么这是模棱两可的MRO?

为什么isnan模棱两可?如何避免?

为什么这个refname模棱两可

为什么非原始类型的隐式转换不是模棱两可的?

为什么当类型变量应该模棱两可时才推断出Integer?

为什么自动装箱会使Java中的某些调用变得模棱两可?

为什么我在JOIN中得到模棱两可的字段名称“ x”

在将重载与类型提升配合使用时,为什么方法调用是模棱两可的?

为什么`System.out.println(null);`给出“方法println(char [])对于类型PrintStream错误是模棱两可的”?

为什么两个功能不是模棱两可的?

为什么初始化列表中的元素数量会导致模棱两可的调用错误?

为什么此C ++代码中的构造函数模棱两可,我该如何解决?

为什么在C#7.2及更低版本中此方法组转换模棱两可?

使用字典替换NumPy数组中的值会产生模棱两可的结果,为什么呢?

该方法对于错误类型是模棱两可的

用幻像类型强制模棱两可的实例

为什么Array和Integer构造函数模棱两可

为什么在加入表时列变得模棱两可?

CDI与@Produces的模棱两可的依赖关系-为什么?

为什么对模版化函数有一个模棱两可的要求?

无功能覆盖的多重继承。为什么模棱两可

为什么在尝试编译代码时出现模棱两可的方法错误?

定义实例时为什么会出现模棱两可的错误?

为什么函数重载在C ++中会产生模棱两可的错误?

为什么实现此通用接口会产生模棱两可的引用?

编译时错误:调用重载方法时模棱两可。为什么?