一个类型的实例化Monoid
问题描述:
我有一个Haskell类型来使一个Map有几个与某个键相关的值。一个类型的实例化Monoid
如果我编译下面的代码:
type Mapa k v = Map k [v]
instance Monoid (Mapa k v) where
--mempty :: Mapa k v
mempty = DM.empty
--mappend :: Mapa k v -> Mapa k v -> Mapa k v
mappend a b = DM.unionWith (++) a b
GHCI会抛出:
Illegal instance declaration for `Monoid (Map k [v])'
(All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Use -XFlexibleInstances if you want to disable this.)
In the instance declaration for `Monoid (Map k [v])'
是否应地图是newtype
或data
; 或有什么问题?
答
在这种情况下,您做想要创建newtype
。当您仅使用type
时,将创建一个类型的同义词,这完全是美容 - Mapa k v
的含义与Map k [v]
的含义完全相同。您想要创建一个新类型,因为普通地图已经有一个与您的新地图重叠的实例,导致混淆行为。
由于您Mapa
型的根本不同的方式表现,你希望它是一个不同的类型:
newtype Mapa k v = Mapa (DM.Map k [v])
接下来,您必须更新您的实例在您的新类型的工作。这需要两个更改:您必须拆开并重新包装您的类型同义词,并且必须添加约束条件。第二个是必要的,因为地图的键必须是相等的,因为地图实际上是内部的树 - 它们必须被排序。因此,新的实例将是这个样子:
instance Ord k => Monoid (Mapa k v) where
mempty = Mapa DM.empty
mappend (Mapa a) (Mapa b) = Mapa $ DM.unionWith (++) a b
对Mapa a
匹配,您可以访问底层Map
;然后你可以使用普通的Map
函数。完成之后,您只需要再次将其包装在Mapa
中。
使用这种不同的类型,你必须打包和打开有点不方便,但这是你必须支付的价格有不同的实例。这也使得Mapa
代表与正常地图完全不同的事实更清楚。
一个小的风格的暗示是,你可以使用反引号定义功能,如缀:
Mapa a `mappend` Mapa b = ...
我认为这是幺更清晰,因为幺操作通常被用作中缀运算符。
最后,您希望使用newtype
而不是data
的原因是因为newtype
没有运行时间开销:它只对类型检查器很重要。从概念上讲,这是有道理的 - 你不是真的创建一个新类型,而是以不同的实例以不同的方式使用相同的基础类型。