Haskell中的类型类解析报告歧义,即使只有一个实例



我正在Haskell中尝试传递类型类实例。众所周知,不能在原始类型类(即(C a b, C b c) => C a c(中声明可传递实例。因此,我尝试定义另一个类来表示原始类的传递闭包。最小代码如下:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Ambig where
class Coe a b where
from :: a -> b
class CoeTrans a b where
from' :: a -> b
instance CoeTrans a a where
from' = id
instance (Coe a b, CoeTrans b c) => CoeTrans a c where
from' = from' . from @a @b
instance Coe Bool Int where
from False = 0
from True = 1
instance Coe Int Integer where
from x = toInteger x

其中CCD_ 2是CCD_ 3的传递闭包。然而,当我试图在CoeTrans中使用from'时,它总是报告歧义:

-- >>> from' True :: Integer
-- Ambiguous type variable ‘b0’ arising from a use of ‘from'’
-- prevents the constraint ‘(Coe Bool b0)’ from being solved.
-- Probable fix: use a type annotation to specify what ‘b0’ should be.
-- These potential instance exist:
--   instance Coe Bool Int
--     -- Defined at /Users/t/Desktop/aqn/src/Ambig.hs:21:10

即使实际上只有一个实例。但根据GHC文档,如果有一个适用的实例,则类型类解析将成功。

为什么会发生这种情况,有什么方法可以解决传递实例问题吗?

我认为您有点误解了文档。他们实际上说,如果存在一个实例,则给定类型的类型类解析将成功。但在你的情况下,没有给出类型。b0不明确。

编译器需要知道b0,然后才能选择Coe Bool b0的实例,即使当前作用域中只有一个实例。这样做是有目的的。其中的关键词是";当前范围";。您可以看到,如果编译器可以选择范围中可用的任何内容,那么您的程序将很容易受到范围中细微变化的影响:您可能会更改导入,或者某些导入的模块可能会更改其内部结构。这可能会导致不同的实例在您当前的范围内出现或消失,这可能会在没有任何警告的情况下导致程序的不同行为。


如果你真的想在任何两种类型之间最多有一条明确的路径,你可以通过向Coe:添加函数依赖来解决它

class Coe a b | a -> b where
from :: a -> b

这将产生两种影响:

  1. 编译器会知道,只要知道a,它总是可以推导出b
  2. 为了方便起见,编译器将禁止定义具有相同a但不同b的多个实例

现在编译器可以看到,由于from'的自变量是Bool,它必须为某些b搜索Coe Bool b的实例,从那里它将确定b必须是什么,从那里可以搜索下一个实例,依此类推


另一方面,如果你真的打算在两个给定的类型之间有多个可能的路径,而编译器只选择一个,那你就倒霉了。编译器原则上拒绝从多种可能性中随机选择一种——参见上面的解释。

相关内容

  • 没有找到相关文章

最新更新