【问题标题】:Instancing Monoid for a Type为一个类型实例化 Monoid
【发布时间】:2013-05-04 08:00:54
【问题描述】:

我在 Haskell 中有一个类型,可以让 Map 有多个与键关联的值。

如果我编译以下代码:

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])'

Mapa 应该是newtype 还是data; 或者有什么问题?

【问题讨论】:

    标签: haskell ghci monoids


    【解决方案1】:

    在这种情况下,您确实想要创建一个newtype。当您只使用type 时,您创建了一个类型同义词,它完全是装饰性的——Mapa k v 的含义与Map k [v] 完全相同。您想创建一个新类型,因为法线贴图已经有一个 Monoid 实例,它会与您的新类型重叠,从而导致混乱的行为。

    由于您的 Mapa 类型的行为方式完全不同,您希望它是不同的类型:

    newtype Mapa k v = Mapa (DM.Map k [v])
    

    接下来,您必须更新您的实例以处理您的新类型。这需要两个更改:您必须解包和重新包装您的类型同义词,并且您必须添加一个Ord k 约束。第二个是必要的,因为映射的键必须是可比较的,并且——因为映射实际上是内部的一棵树——它们必须是有序的。所以你的新实例看起来像这样:

    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 没有运行时开销:它只对类型检查器很重要。从概念上讲,这是有道理的——您实际上并不是在创建一个新类型,而是在不同的实例中以不同的方式使用相同的底层类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多