如何定义可重叠的依赖实例?



我有一个类似这样的类型类

class Stringify x where
stringify :: x -> String

,我还有两个类似的类型类

class LTextify x where
ltextify :: x -> L.Text
class Textify x where
textify :: x -> T.Text

我想定义依赖类型类

class (LTextify x) => Stringify x where
stringify = L.unpack . ltextify
class (Textify x) => Stringify x where
stringify = T.unpack . textify

但是haskell当然抱怨"重复的实例声明";这当然是真的。我试过用OVERLAPPABLE和overlap来玩,但是没有用。我想要的是,如果一个类有LTextify的实例,那么应该使用它。如果没有,那么如果它有一个texttify的实例,那么应该使用它来实现Stringify。我希望一个类也能够显式地定义Stringify的实例,然后重叠Textify或LTextify的可能存在的实例。当然是以确定性的方式。

我假设最后两个应该是实例声明而不是类声明?

instance (LTextify x) => Stringify x where ...
instance (Textify x) => Stringify x where ...

根据实例是否在Haskell中存在而有不同的行为,因为孤立实例意味着可用实例的列表可能会根据导入以不可预测的方式发生变化。我建议(如果可能的话)将Stringify作为TextifyLTextify的超类。

此外,这些特定的实例将是不可能的,因为GHC的实例解析的第一步是放弃所有约束并检查哪个实例将在结构上匹配,所以它看起来像这样:

instance Stringify x
instance Stringify x

根本没有办法区分这两个,所以ghc必须在其中一个中任意选择,并希望它是正确的。

为了使用OVERLAPPINGpragmas,需要有一个实例严格地比另一个更具体,如

instance C a => Foo [a]
instance {-# OVERLAPPING #-} Foo [Char]

有关其工作原理的更深入解释,请参阅用户手册中的这一节。这里有一个相关的引用:

GHC要求应该使用哪个实例声明来解析类型类约束是明确的。GHC还提供了一种放松实例解析的方法,通过允许多个实例匹配,只要有一个最特定的.


还有一个被弃用的扩展IncoherentInstances,它允许多个匹配实例,即使一个没有严格地比另一个更具体,但即使在这里也不能使用,因为两个实例在结构上是相同的。

IncoherentInstances将任意选择一个实例,因此应该尽可能避免使用它,只有在与哪个实例匹配无关的情况下才使用它。


编辑:定义实例时减少样板文件的一种方法是使用DerivingVia和newtype包装器:

newtype UsingLTextify a = UsingLTextify { unwrapLT :: a }
newtype UsingTextify  a = UsingTextify  { unwrapT  :: a }
instance LTextify a => Stringify (UsingLTextify a) where
stringify = L.unpack . ltextify . unwrapLT
instance Textify a  => Stringify (UsingTextify a) where
stringify = T.unpack . textify . unwrapT

然后你可以用它来派生这样的实例

{-# LANGUAGE DerivingVia #-}
data Foo = ...
deriving Stringify via UsingLTextify Foo
instance LTextify Foo where ...

如果您无法控制数据类型,并且不想添加孤立实例(您应该尽可能避免),这些新类型也可以直接使用,通过将它们包装在数据周围,为其提供相关实例。

最新更新