Haskell 中的类型类解析报告含糊不清,即使只有一个实例
Typeclass resolution in Haskell reporting ambiguity even if there is only one instance
我正在 Haskell 中试验传递类型 class 实例。众所周知,不能在原始类型class(即(C a b, C b c) => C a c
)中声明一个传递实例。因此,我尝试定义另一个 class 来表示原始 class 的传递闭包。最小代码如下:
{-# 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
其中 CoeTrans
是 Coe
的传递闭包。但是,当我尝试在 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 docs 类型class 解决方案将成功,前提是存在一个适用的实例。
为什么会出现这种情况,有什么办法可以解决传递实例问题吗?
我认为您对文档有一点误解。他们真的说,如果存在一个实例,给定类型 的类型类解析 将会成功。但在你的情况下,没有给出类型。 b0
有歧义。
编译器在选择 Coe Bool b0
的实例之前需要知道 b0
,即使当前作用域中只有一个实例。这是故意这样做的。那里的关键词是“当前范围”。你看,如果编译器可以只选择范围内可用的任何内容,你的程序将容易受到范围内细微变化的影响:你可能会更改你的导入,或者你的某些导入模块可能会更改它们的内部结构。这可能会导致不同的实例在您当前的范围内出现或消失,这可能会导致您的程序在没有任何警告的情况下出现不同的行为。
如果你真的打算在任何两种类型之间总是最多只有一条明确的路径,你可以通过向Coe
添加函数依赖来解决它:
class Coe a b | a -> b where
from :: a -> b
这将产生两个影响:
- 编译器知道它总是可以通过知道
a
来推断 b
。
- 并且为了促进这一点,编译器将禁止定义具有相同
a
但不同 b
的多个实例。
现在编译器可以看到,由于 from'
的参数是 Bool
,它必须为某些 b
搜索 Coe Bool b
的实例,并且从在那里它将确定 b
必须是什么,然后它可以从那里搜索下一个实例,依此类推。
另一方面,如果您真的打算在两个给定类型之间存在多个可能的路径,并且让编译器只选择一个 - 您真不走运。编译器原则上拒绝随机选择多种可能性中的一种 - 请参阅上面的解释。
我正在 Haskell 中试验传递类型 class 实例。众所周知,不能在原始类型class(即(C a b, C b c) => C a c
)中声明一个传递实例。因此,我尝试定义另一个 class 来表示原始 class 的传递闭包。最小代码如下:
{-# 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
其中 CoeTrans
是 Coe
的传递闭包。但是,当我尝试在 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 docs 类型class 解决方案将成功,前提是存在一个适用的实例。
为什么会出现这种情况,有什么办法可以解决传递实例问题吗?
我认为您对文档有一点误解。他们真的说,如果存在一个实例,给定类型 的类型类解析 将会成功。但在你的情况下,没有给出类型。 b0
有歧义。
编译器在选择 Coe Bool b0
的实例之前需要知道 b0
,即使当前作用域中只有一个实例。这是故意这样做的。那里的关键词是“当前范围”。你看,如果编译器可以只选择范围内可用的任何内容,你的程序将容易受到范围内细微变化的影响:你可能会更改你的导入,或者你的某些导入模块可能会更改它们的内部结构。这可能会导致不同的实例在您当前的范围内出现或消失,这可能会导致您的程序在没有任何警告的情况下出现不同的行为。
如果你真的打算在任何两种类型之间总是最多只有一条明确的路径,你可以通过向Coe
添加函数依赖来解决它:
class Coe a b | a -> b where
from :: a -> b
这将产生两个影响:
- 编译器知道它总是可以通过知道
a
来推断b
。 - 并且为了促进这一点,编译器将禁止定义具有相同
a
但不同b
的多个实例。
现在编译器可以看到,由于 from'
的参数是 Bool
,它必须为某些 b
搜索 Coe Bool b
的实例,并且从在那里它将确定 b
必须是什么,然后它可以从那里搜索下一个实例,依此类推。
另一方面,如果您真的打算在两个给定类型之间存在多个可能的路径,并且让编译器只选择一个 - 您真不走运。编译器原则上拒绝随机选择多种可能性中的一种 - 请参阅上面的解释。