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

时间:2018-01-21 17:34:22

标签: haskell

我想知道如何在不使用instance Show的情况下,在以下代码段Type families文章中为Gmap数据类型实施deriving 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

2 个答案:

答案 0 :(得分:2)

您不能在GMap之类的数据系列的数据构造函数上进行模式匹配,而不知道它的密钥类型 - 即它不像GADT,因为它& #39;开放,所以不可能涵盖所有情况。因此,如果要实现通用节目,则需要在不直接访问表示的情况下执行此操作。我有两种选择:

Catch-all instance

在玩了一下之后,我能想到的最简单的方法是在类中添加一个方法,以便在实例中使用。

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

然后你可以创建一个通用的catch-all实例。

instance (GMapKey k, Show v) => Show (GMap k v) where
  show = showGMap

这比我想要的要简单一点,但它并不是太糟糕。我对这种方法的主要哀叹是,它排除了实例上的deriving 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

字典方法

如果您需要使用deriving Show,则可以使用constraints包以更现代的方式进行操作。

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
import Data.Constraint

它还涉及添加方法,但不返回String,而是返回整个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))

您可以使用deriving进行实例化,showGMap实例始终相同。

 instance GMapKey Int where
   data GMap Int v        = GMapInt (Data.IntMap.IntMap v)
       deriving (Show)
   ...
   showGMap = Dict

(您甚至可以使用DefaultSignatures来避免在实例中提及showGMap!)

不幸的是,一个包罗万象的实例仍然会与此重叠,因此我们不会为Show设置全局GMap实例。但是,我们可以使用withDict

在我们需要的任何地方制作一个
example :: forall k v. (GMapKey k, Show v) => GMap k v -> String
example gmap = withDict @k @v (show gmap)

以不同的方式烦人。幸运的是,我们只需要在需要泛型 Show (GMap k v)实例时执行此操作 - 如果我们已经知道k是什么,那么我们派生的特定Show实例将会工作

也许有办法让两全其美?

答案 1 :(得分:1)

GMapInt构造函数特定于GMap Int,因此除了{{GMap k之外,您无法使用k构建/解构Int 1}}。

你可能想要这个实例:

instance Show (GMap Int v) where                                                                                                                                      
  show (GMapInt _) = undefined

或者,如果Int唯一的键类型,您的地图可以显示(我觉得很奇怪),

instance (k ~ Int) => Show (GMap k v) where                                                                                                                                      
  show (GMapInt _) = undefined

后者具有以下优点:类型检查器在解析之前不需要知道密钥是Intshow empty(在没有显式类型签名的情况下,它不会在第一种方法中编译),而是可以利用密钥类型必须始终Int的知识。通常很有用,但在您的应用程序中可能不是正确的。