Haskell 类嵌套数据类型中的实例实现

Haskell 类嵌套数据类型中的实例实现,haskell,Haskell,我想知道如何为Gmap数据类型实现instance Show,而不使用派生Show class GMapKey k where data GMap k :: * -> * empty :: GMap k v lookup :: k -> GMap k v -> Maybe v insert :: k -> v -> GMap k v -> GMap k v instance GMapKey Int where

我想知道如何为
Gmap
数据类型实现
instance Show
,而不使用
派生Show

class GMapKey k where
  data GMap k :: * -> *
  empty       :: GMap k v
  lookup      :: k -> GMap k v -> Maybe v
  insert      :: k -> v -> GMap k v -> GMap k v

instance GMapKey Int where
  data GMap Int v        = GMapInt (Data.IntMap.IntMap v)
  empty                  = GMapInt Data.IntMap.empty
  lookup k   (GMapInt m) = Data.IntMap.lookup k m
  insert k v (GMapInt m) = GMapInt (Data.IntMap.insert k v m)
对于这一启动实施:

instance Show (GMap k v) where                                                                                                                                      
  show (GMapInt _) = undefined
编译器抛出:

* Couldn't match type `k' with `Int'
  `k' is a rigid type variable bound by
    the instance declaration
    at /home/x/src/GMapAssoc.hs:27:10
  Expected type: GMap k v
    Actual type: GMap Int v
* In the pattern: GMapInt _
  In an equation for `show': show (GMapInt _) = undefined
  In the instance declaration for `Show (GMap k v)'
* Relevant bindings include
    show :: GMap k v -> String
除了主要问题之外,我想了解为什么编译器在这种情况下不抱怨:

instance Show (GMap k v) where                                                                                                                                      
  show _ = undefined

GMapInt
构造函数是特定于
GMap Int
的,因此除了
Int
之外,您不能使用它为
k
构造/解构
GMap k

您可能需要此实例:

instance Show (GMap Int v) where                                                                                                                                      
  show (GMapInt _) = undefined
或者,如果
Int
是唯一可以显示其映射的键类型(我觉得很奇怪)


后者的优点是,类型检查器在解析前不需要知道密钥是
Int
,例如
显示空的
(在第一种方法中,如果没有明确的类型签名,它将无法编译),相反,它可以利用密钥类型必须始终是
Int
的知识。通常很有用,但在您的应用程序中可能不是正确的东西。

如果不知道它的键类型,就无法对数据族(如
GMap
)的数据构造函数进行模式匹配,也就是说,它不像GADT,因为它是开放的,所以不可能涵盖所有情况。因此,如果您想要实现一个通用的显示,您需要在不直接访问表示的情况下实现它。我有两个选择:

包罗万象 在进行了一些讨论之后,我能想到的最简单的方法是向类中添加一个方法,以便在实例中使用

class GMapKey k where
  data GMap k :: * -> *
  empty       :: GMap k v
  lookup      :: k -> GMap k v -> Maybe v
  insert      :: k -> v -> GMap k v -> GMap k v
  showGMap    :: (Show v) => GMap k v -> String
instance (GMapKey k, Show v) => Show (GMap k v) where
  show = showGMap
然后,您可以创建一个通用的catch-all实例

class GMapKey k where
  data GMap k :: * -> *
  empty       :: GMap k v
  lookup      :: k -> GMap k v -> Maybe v
  insert      :: k -> v -> GMap k v -> GMap k v
  showGMap    :: (Show v) => GMap k v -> String
instance (GMapKey k, Show v) => Show (GMap k v) where
  show = showGMap
这是一个有点不顺利,我会希望,但它不是太糟糕。我对这种方法的主要遗憾是,它排除了对实例的
派生Show
,即

instance GMapKey Int where
  data GMap Int v        = GMapInt (Data.IntMap.IntMap v)
      deriving (Show)
  ...
是非法的,因为它与catch-all实例重叠。如果类型变得复杂,这可能是一种痛苦。下一种方法没有这个问题

无论如何,现在我们有了一个实例,可以像往常一样使用它

example :: (GMapKey k, Show v) => GMap k v -> String
example gmap = show gmap
字典法 如果需要使用
派生Show
,可以使用该软件包以更现代的方式进行

它还包括添加一个方法,但不是返回一个
字符串
,而是返回一个完整的
Show
字典

class GMapKey k where
  data GMap k :: * -> *
  empty       :: GMap k v
  lookup      :: k -> GMap k v -> Maybe v
  insert      :: k -> v -> GMap k v -> GMap k v
  showGMap    :: (Show v) => Dict (Show (GMap k v))
您可以使用
派生
进行实例化,而
showGMap
实例总是相同的

 instance GMapKey Int where
   data GMap Int v        = GMapInt (Data.IntMap.IntMap v)
       deriving (Show)
   ...
   showGMap = Dict
(您甚至可以使用来避免在实例中提到
showGMap

不幸的是,catch-all实例仍将与此重叠,因此我们不会为
GMap
s提供全局
Show
实例。但是,我们可以在任何需要的地方使用

这是另一种恼人的方式。幸运的是,只有当我们需要一个通用的
Show(GMap k v)
实例时,我们才需要这样做——如果我们已经知道
k
是什么,那么我们派生的特定
Show
实例就会工作


也许有一种方法可以两全其美?

实例展示(GMap Int v)我也尝试过,但是ghc 8.0.2抛出异常
非法实例声明来展示(GMap Int v)`它还说明了需要做些什么才能让它工作。根据你的建议,我实现了如下
实例(k~Int,Show v)=>Show(GMap k v)其中Show(GMapInt k)=“GMapInt(“++(Show k)++”)”
但是我想实现一个通用的
实例Show GMap
,不受
Int
的限制。
example :: forall k v. (GMapKey k, Show v) => GMap k v -> String
example gmap = withDict @k @v (show gmap)