2013-05-04 82 views
5

我有一个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])' 

是否应地图是newtypedata; 或有什么问题?

回答

11

在这种情况下,您想要创建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没有运行时间开销:它只对类型检查器很重要。从概念上讲,这是有道理的 - 你不是真的创建一个新类型,而是以不同的实例以不同的方式使用相同的基础类型。