我想知道如何在不使用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
答案 0 :(得分:2)
您不能在GMap
之类的数据系列的数据构造函数上进行模式匹配,而不知道它的密钥类型 - 即它不像GADT,因为它& #39;开放,所以不可能涵盖所有情况。因此,如果要实现通用节目,则需要在不直接访问表示的情况下执行此操作。我有两种选择:
在玩了一下之后,我能想到的最简单的方法是在类中添加一个方法,以便在实例中使用。
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
后者具有以下优点:类型检查器在解析之前不需要知道密钥是Int
。 show empty
(在没有显式类型签名的情况下,它不会在第一种方法中编译),而是可以利用密钥类型必须始终为Int
的知识。通常很有用,但在您的应用程序中可能不是正确的。