您不能从类型族TypeGeneStr d
返回到原始类型d
。当 GHC 说“NB:`TypeGeneStr'
是一个类型函数,并且可能不是单射的”时,这就是它告诉你的:仅仅知道这TypeGeneStr d ~ TypeGeneStr d'
并不意味着d ~ d'
(~
类型相等在哪里)。
因此,当你有一个类型类函数,它的名称中只有类型同义词并且从不引用原始参数时,你将永远无法调用它——这描述了类型类中的每个函数,除了geneTypeRep
unimplementable showExpandTypeArgs
。失败的原因是 GHC 无法确定要使用类型类的哪个实例。考虑如果我允许TypeGeneStr
和/或TypeGeneRep
碰撞会发生什么,如下例所示:
class HeukaryaGene Int where
type TypeGeneStr Int = String
type TypeGeneRep Int = ()
showGeneTypeRep () = "Int"
class HeukaryaGene Bool where
type TypeGeneStr Bool = String
type TypeGeneRep Bool = ()
showGeneTypeRep () = "Bool"
那么应该showGeneTypeRep ()
是"Int"
还是"Bool"
?或者它甚至应该是一个String
?如果我有
class HeukaryaGene () where
type TypeGeneStr () = ()
type TypeGeneRep () = ()
showGeneTypeRep () = ()
那么showGeneTypeRep ()
也可以()
。showExpandTypeArgs
这在您对: inside的定义中咬住了您typo
,而 GHC 可以正确地确定它dynam :: d
,因此geneTypeRep dynam :: TypeGeneRep d
它不知道该showGeneTypeRep :: TypeGeneRep d -> TypeGeneStr d
选择哪个。而且您的电话也lexByArrow
遇到了类似的问题:GHC 只知道typo :: TypeGeneStr d'
对于某些人d'
来说,并且它不知道lexByArrow :: TypeGeneStr d' -> [TypeGeneStr d']
要选择哪个版本(它确实知道TypeGeneRep d ~ TypeGeneRep d'
,但这还不足以决定)。
在我的 GHC(7.4.2)上,我还收到一个错误,即d
您的类型签名中Typo
的d
与类型类头中的不同,但仅删除该类型签名(显然)并没有得到任何东西类型检查。
(我应该说:您在这里收到的错误消息肯定令人困惑,因为它们没有说明发生了什么以及为什么会出现错误。)
我能想到的最简单的解决方法是d
为所有不需要的函数包含一个虚拟参数。您永远不会分析该参数;它只是用来引导实例选择。在呼叫站点,您可以使用undefined :: d
一些具体类型d
。看起来像这样:
class HeukaryaGene (d :: *) where
type TypeGeneStr d :: *
type TypeGeneRep d :: *
lexByArrow :: d -> TypeGeneStr d -> [TypeGeneStr d]
geneTypeRep :: d -> TypeGeneRep d
geneTypeRepArgs :: d -> TypeGeneRep d -> [TypeGeneRep d]
showGeneTypeRep :: d -> TypeGeneRep d -> TypeGeneStr d
showExpandTypeArgs :: d -> [TypeGeneStr d]
showExpandTypeArgs dynam = lexByArrow dynam typo
where
typo = showGeneTypeRep dynam $ geneTypeRep dynam
如果您不喜欢这样,我能想到的最小侵入性的解决方法是从类型系列转到数据系列。数据族类似于类型族,不同之处在于您使用关键字data
并且它们定义了全新的数据类型。这很重要:就像普通的数据结构一样,数据族是生成的,因此是单射的。(数据族的每个实例化都会生成一个全新的类型。)看起来像这样:
class HeukaryaGene (d :: *) where
data TypeGeneStr d :: *
data TypeGeneRep d :: *
lexByArrow :: TypeGeneStr d -> [TypeGeneStr d]
geneTypeRep :: d -> TypeGeneRep d
geneTypeRepArgs :: TypeGeneRep d -> [TypeGeneRep d]
showGeneTypeRep :: TypeGeneRep d -> TypeGeneStr d
showExpandTypeArgs :: d -> [TypeGeneStr d]
showExpandTypeArgs dynam = lexByArrow typo
where
typo = showGeneTypeRep $ geneTypeRep dynam
我所做的唯一更改是更改type
并data
删除您的类型签名typo
。这个版本的问题是,HeukaryaGene
现在要实例化,你必须写类似
instance HeukaryaGene Int where
-- You can instantiate data families with newtypes, too.
newtype TypeGeneStr Int = TGSInt String
data TypeGeneRep Int = TGRInt
showGeneTypeRep TGRInt = TGSInt "Int"
也就是说,可能有很多(解)包装要做。但这会起作用。
你也可以在这里使用函数依赖,但你实际上是在复制这个解决方案及其缺点;这个想法是你有class HeukaryaGene d tgs tgr | d -> tgs tgr, tgs -> d, tgr -> d
,所以知道 , 或 中的任何一个d
就tgs
足以tgr
推断出其他两个。这看起来不错,但意味着任何给定类型只能用作基因字符串或基因代表类型一次,因此与上述数据系列版本具有基本相同的缺点。
另一种解决方案是,如果您从不调用lexByArrow
或showGeneTypeRep
在外部调用,则将showExpandTypeArgs
它们从类型类中删除,并让用户实现showExpandTypeArgs
它们的可能方式。但是,这对 没有帮助geneTypeRepArgs
,这将不得不去
最后一个解决方案是不使用类型类,而是自己处理字典。这将是一个非常激进的重新设计(尽管不一定是坏的),但如果你真的想要 and 的类型同义词TypeGeneStr
,TypeGeneRep
这是我能想到的唯一方法。看起来像这样:
data HeukaryaGene d tgs tgr =
HeukaryaGene { lexByArrow :: tgs -> [tgs]
, geneTypeRep :: d -> tgr
, geneTypeRepArgs :: tgr -> [tgr]
, showGeneTypeRep :: tgr -> tgs }
showExpandTypeArgs :: d -> [tgs]
showExpandTypeArgs dynam = lexByArrow typo
where typo = showGeneTypeRep $ geneTypeRep dynam
然后,以前具有 type 的函数HeukaryaGene d => t
将具有 type HeukaryaGene d tgs tr -> t
。使用-XRecordWildCards
(和作为模式)在这里会有所帮助,允许您编写
getTypeRepArgs HeukaryaGene{..} = geneTypeRepArgs . geneTypeRep
如果您希望某些字典具有不同的 实现showExpandTypeArgs
,则必须稍微不同地构造事物:
data HeukaryaGene d tgs tgr =
HeukaryaGene { lexByArrow :: tgs -> [tgs]
, geneTypeRep :: d -> tgr
, geneTypeRepArgs :: tgr -> [tgr]
, showGeneTypeRep :: tgr -> tgs
, showExpandTypeArgs :: d -> [tgs] }
showExpandTypeArgsDefault :: HeukaryaGene d tgs tgr -> d -> [tgs]
showExpandTypeArgsDefault HeukaryaGene{..} dynam = lexByArrow typo
where typo = showGeneTypeRep $ geneTypeRep dynam
然后,您将在初始化期间打结:
hgInt :: HeukaryaGene Int String ()
hgInt = HeukaryaGene { lexByArrow = words
, geneTypeRep = const ()
, geneTypeRepArgs = const []
, showGeneTypeRep = const "Int"
, showExpandTypeArgs = showExpandTypeArgsDefault hgInt }
事实上,在这个解决方案中,d
可能是多余的 in geneTypeRep
,因此showExpandTypeArgs
,以及它HeukaryaGene
本身:如果像 in 一样Typeable
,你只是d
用来选择类型类,那么你就不需要它了。但这将取决于您的具体情况。