想象一下,我们有一个使用库的Haskell程序。该程序从其依赖项之一为类型T提供类型类TC实例。在同一个库的下一个版本中,库作者为类型T提供了类型TC的另一个实例。
我们想要使用两个类型类实例。我们怎么能这样做?
P.S。 newtype解决方案不起作用。这两个实例都驻留在我们无法控制的库中。
P.P.S。我没有真实代码的例子。这是一个理论问题。我只是想了解类型类如何与库可组合性一起工作。
答案 0 :(得分:5)
- 在程序中,类型不能多次声明为特定类的实例。
所以在标准的Haskell中是不可能的。
我不知道任何GHC扩展允许您在GHC中执行此操作。
这是(一个?)为什么孤立实例(在类型和类型类中定义不同模块中的实例)通常被认为是个坏主意的原因。
答案 1 :(得分:4)
通常,同一类型的多个类型实例是不可能的。但是,如果类型是在不同的包中定义的,或者是同一包的旧版本,则ghc会将其视为不同的类型。理论上你可以foo-1.1
定义Foo
及其实例,foo-1.2
定义Foo
及其实例,并将两者结合使用。
然而,在实践中,这不会很好。功能仅适用于一种类型或另一种类型。当您编写一个在Foo
上运行的函数时,它将仅对一个特定Foo
运行,而不是两个都运行。基本上你有两个完全独立,无可争议的类型。使用它会很尴尬,完全不同于构建它的难度。
答案 2 :(得分:3)
我可以肯定地说,使用Cabal只能依赖同一个库的单个版本。
虽然您可以至少将实例的替代版本的源代码复制粘贴到项目中,但您仍然只能为每个模块导入其中一个。如果您将冲突的实例导入模块,您将遇到不可判定的实例问题,对此您将没有实际的解决方案。
说实话我无法想象为什么人们可能希望为同一个库发出的相同类型拥有相同类的不同实例。这似乎非常不切实际。虽然我对你的情况有什么帮助的猜测是有两个带有相应实例的类型类:一个来自当前版本的库而另一个 - 一个重命名的旧代码的源代码副本。
答案 3 :(得分:3)
如果您想要更好的灵活性和可组合性,那么将类型类重新作为记录。 RankNTypes
可能是必要的。
例如,以下是如何重新启用Applicative类型
{-# LANGUAGE RankNTypes #-}
data ApplicativeInstance f = ApplicativeInstance
{ pure :: forall a. a -> f a
, amap :: forall a b. (a -> b) -> f a -> f b
, ap :: forall a b. f (a -> b) -> f a -> f b
}
listApplicative = ApplicativeInstance
{ pure = \a -> [a]
, amap = map
, ap = \fs xs -> case fs of
[] -> []
f:fs' -> map f xs ++ ap cartesianListApplicative fs' xs
}
zipListApplicative = ApplicativeInstance
{ pure = \a -> [a]
, amap = map
, ap = \fs xs -> case (fs, xs) of
([], _) -> []
(_, []) -> []
(f:fs', x:xs') -> f x : ap zipListApplicative fs' xs'
}
现在,我们获得了指定我们想要的实例的能力。但是,我们失去了隐式选择实例的能力:现在必须明确选择。
ghci> ap listApplicative [(+1), (*3)] [1 .. 5]
[2,3,4,5,6,3,6,9,12,15]
ghci> ap zip
zip zipListApplicative zipWith3
zip3 zipWith
ghci> ap zipListApplicative [(+1), (*3)] [1 .. 5]
[2,6]
另见: http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/
答案 4 :(得分:2)
目前无法在依赖关系树上链接同一个软件包的不同版本,因为它会导致dependency conflict。
但是,如果GHC status update video中所述,这将是可能的,只要它们在同一个库中只使用一个版本我猜。