2

我使用类型 Families 重构了我的代码,下面是项目中使用的类型类。

class HeukaryaGene (d :: *) where
  type TypeGeneStr d :: *
  type 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 :: TypeGeneStr d

但是 ghc 总是抱怨很多相同的事情:

Could not deduce (TypeGeneStr d ~ TypeGeneStr d2)
from the context (HeukaryaGene d)
  bound by the class declaration for `HeukaryaGene'
  at AI/Heukarya/Gene.hs:(22,1)-(42,63)
NB: `TypeGeneStr' is a type function, and may not be injective
The type variable `d2' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Expected type: [TypeGeneStr d]
  Actual type: [TypeGeneStr d2]
In the return type of a call of `lexByArrow'
In the expression: lexByArrow typo
In an equation for `showExpandTypeArgs':
    showExpandTypeArgs dynam
      = lexByArrow typo
      where
          typo = showGeneTypeRep $ geneTypeRep dynam :: TypeGeneStr d

我想知道我误会了什么

4

3 回答 3

4

您不能从类型族TypeGeneStr d返回到原始类型d。当 GHC 说“NB:`TypeGeneStr'是一个类型函数,并且可能不是单射的”时,这就是它告诉你的:仅仅知道这TypeGeneStr d ~ TypeGeneStr d'并不意味着d ~ d'~类型相等在哪里)。

因此,当你有一个类型类函数,它的名称中只有类型同义词并且从不引用原始参数时,你将永远无法调用它——这描述了类型类中的每个函数,除了geneTypeRepunimplementable 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您的类型签名中Typod与类型类头中的不同,但仅删除该类型签名(显然)并没有得到任何东西类型检查。

(我应该说:您在这里收到的错误消息肯定令人困惑,因为它们没有说明发生了什么以及为什么会出现错误。)


我能想到的最简单的解决方法是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

我所做的唯一更改是更改typedata删除您的类型签名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,所以知道 , 或 中的任何一个dtgs足以tgr推断出其他两个。这看起来不错,但意味着任何给定类型只能用作基因字符串或基因代表类型一次,因此与上述数据系列版本具有基本相同的缺点。


另一种解决方案是,如果您从不调用lexByArrowshowGeneTypeRep在外部调用,则将showExpandTypeArgs它们从类型类中删除,并让用户实现showExpandTypeArgs它们的可能方式。但是,这对 没有帮助geneTypeRepArgs,这将不得不去


最后一个解决方案是不使用类型类,而是自己处理字典。这将是一个非常激进的重新设计(尽管不一定是坏的),但如果你真的想要 and 的类型同义词TypeGeneStrTypeGeneRep这是我能想到的唯一方法。看起来像这样:

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用来选择类型类,那么你就不需要它了。但这将取决于您的具体情况。

于 2013-07-20T17:31:21.643 回答
1

我根据以下内容找到了这个优雅的解决方案:Incomprehensible error message with type family

类型类定义:

class (
  UnTypeGeneStr (TypeGeneStr d) ~ d, UnTypeGeneRep (TypeGeneRep d) ~ d
  ) => HeukaryaGene d where
  type TypeGeneStr d :: *
  type TypeGeneRep d :: *
  type UnTypeGeneStr o :: *
  type UnTypeGeneRep o :: *

实例定义:

instance HeukaryaGene Dynamic where
  type TypeGeneStr Dynamic = String
  type TypeGeneRep Dynamic = TypeRep
  type UnTypeGeneStr String  = Dynamic
  type UnTypeGeneRep TypeRep = Dynamic

感谢大家的帮助!!

于 2013-07-20T18:59:21.340 回答
0

我知道这是一个老问题,但 GHC 8.0 增加了对单射类型同义词的支持。如果您启用TypeFamilyDependencies,您的示例应使用以下内容:

class HeukaryaGene d where
  type TypeGeneStr d = s | s -> d
  type TypeGeneRep d = r | r -> d
  ...
于 2017-03-23T18:55:42.650 回答