【问题标题】:Making Data.Lazy.Map an instance of Num使 Data.Lazy.Map 成为 Num 的实例
【发布时间】:2016-07-27 20:59:13
【问题描述】:

在一个项目中,我经常需要总结列表和哈希。我最终得到了Num 实例的以下两个声明

{-# LANGUAGE FlexibleInstances #-}
module Tweak where
import Data.List
import qualified Data.Map.Lazy as M
import Control.Applicative

instance (Applicative f, Num b) => Num (f b) where
  negate      = fmap negate
  (+)         = liftA2 (+)
  (*)         = liftA2 (*)
  fromInteger = pure . fromInteger
  abs         = fmap abs
  signum      = fmap signum

instance (Ord k, Num b) => Num(M.Map k b) where
  negate      = fmap negate
  (+)         = M.unionWith (+)
  (*)         = M.unionWith (*)
  fromInteger = undefined
  abs         = fmap abs
  signum      = fmap signum

在 ghci 中运行 (M.fromList [(2 :: Int, 3 :: Int)]) + (M.fromList [(1, 2)]) 会产生以下错误

<interactive>:2:37:
    Overlapping instances for Num (M.Map Int Int)
      arising from a use of ‘+’
    Matching instances:
      instance (Ord k, Num b) => Num (M.Map k b)
        -- Defined at Tweak.hs:15:10
      instance (Applicative f, Num b) => Num (f b)
        -- Defined at Tweak.hs:7:10
    In the expression:
      (M.fromList [(2 :: Int, 3 :: Int)]) + (M.fromList [(1, 2)])
    In an equation for ‘it’:
        it = (M.fromList [(2 :: Int, 3 :: Int)]) + (M.fromList [(1, 2)])

如果我理解错误,Haskell 认为 M.Map IntApplicative 的一个实例。情况似乎并非如此。 :i M.Map Int 产量

type role M.Map nominal representational
data M.Map k a
  = containers-0.5.6.2:Data.Map.Base.Bin {-# UNPACK #-}containers-0.5.6.2:Data.Map.Base.Size
                                         !k
                                         a
                                         !(M.Map k a)
                                         !(M.Map k a)
  | containers-0.5.6.2:Data.Map.Base.Tip
    -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
instance (Ord k, Num b) => Num (M.Map k b)
  -- Defined at Tweak.hs:15:10
instance (Eq k, Eq a) => Eq (M.Map k a)
  -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
instance Functor (M.Map k)
  -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
instance (Ord k, Ord v) => Ord (M.Map k v)
  -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
instance (Ord k, Read k, Read e) => Read (M.Map k e)
  -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
instance (Show k, Show a) => Show (M.Map k a)
  -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
instance Foldable (M.Map k)
  -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
instance Traversable (M.Map k)
  -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
instance Ord k => Monoid (M.Map k v)
  -- Defined in ‘containers-0.5.6.2:Data.Map.Base’
data Ability = ... | Int | ...  -- Defined at Character.hs:60:41

data Int = GHC.Types.I# GHC.Prim.Int#   -- Defined in ‘GHC.Types’
instance Bounded Int -- Defined in ‘GHC.Enum’
instance Enum Int -- Defined in ‘GHC.Enum’
instance Eq Int -- Defined in ‘GHC.Classes’
instance Integral Int -- Defined in ‘GHC.Real’
instance Num Int -- Defined in ‘GHC.Num’
instance Ord Int -- Defined in ‘GHC.Classes’
instance Read Int -- Defined in ‘GHC.Read’
instance Real Int -- Defined in ‘GHC.Real’
instance Show Int -- Defined in ‘GHC.Show’

我真的不明白这个错误。非常欢迎任何帮助。

【问题讨论】:

  • Haskell 在查看约束之前执行实例解析,因此您实际上定义了一个实例forall f b. Num (f b),这是非常通用的,可能不是您想要的。只有在选择了一个实例之后才会检查约束。
  • 是的,这肯定不是我想要的。知道如何解决这个问题吗?
  • 这可能是您所要求的,但在这里交付您所要求的,需要付出高昂的代价。在任何类型的 * -> * 上没有其他 Num 实例。更好的机制是创建一个newtype ANum f a = ANum (f a),然后将您的实例烘焙到新类型中。这样你就不会在任何人都会写的 * -> * 类型的东西上与所有其他可能的 num 实例发生冲突,并且用户可以将他们的数据包装成与你的用例兼容的形式。考虑 newtype Poly a = Poly [a] 它将与您的实例重叠,但肯定不应该使用 []` 的应用程序来完成!

标签: haskell


【解决方案1】:

为什么人们总是认为定义这种实例是个好主意?

类型类都是关于允许安全地编写泛型函数。这通过指定某些方法和关于它们应该如何一起玩的明确法律来工作。比如,单子定律等。
Num 基本上代表了ring 的数学概念,加上定义metric 的一种半心半意的方法。仅当您实际上将其限制为 数字类型 时,这才有效。一般的数字集合,就像您的Applicative 实例一样,可能根本不遵守这些法律。

因此,请远离此类情况。我怀疑您尝试使用它们的方式无论如何都与通用算法无关...如果您只想为M.unionWith (+) 使用简洁的运算符名称,那么您就是在滥用类型类。您可以只定义一个普通的单态运算符(+&lt;)(或者,至少不是临时多态的——如果它适用于(Num n, Ord k) =&gt; M.Map k n 可能没问题)或类似的东西。

如果您认为确实需要类多态性,那么请寻找合适的类! Additive 可能有意义。瞧,it actually has an instance for Map 已经...


我应该提到第三种可能性,这实际上是我通常推荐的并且在某种意义上更接近您的Num 实例想法:VectorSpace class。和Num 一样,这实际上是一类值类型,而不是函子,但与Num 不同的是,它没有abs 等等。

Additive 不同,此软件包不附带Map 实例,但有a dedicated type 仅用于此功能实例是标准的,因此,使用矢量空间的^+^ 实际上可以添加向上a -&gt; Map k Int 值。

【讨论】:

  • 我对@9​​87654344@ 的Applicative 声明是规范的。我以逐点的方式提升数值运算。 M.Map k n 只是从 kn 的一个函数,因此在其上定义操作点也是非常有意义的。仔细查看Additive 中的代码,M.Map 的声明与我的非常相似。但是我不喜欢,因为它给M.Map Int Char 添加了一个对我来说毫无意义的添加。另外我没有提到环结构,我将Num 视为一个低级类别,它允许计算机对那些正是我想要的对象执行基本计算。
  • Numcategory 怎么样?这对我来说真的没有意义。 ——Additive 没有给Map Int Char 加分,是什么给了你这样的印象?它只会将 Num 实例提升到某个仿函数内的逐点 Num 操作,即正是您需要的。
  • 好吧,Num 是集合的集合,所以我习惯称它为类别,即使在这种情况下,态射是微不足道的。我之所以有这种感觉是因为instance Ord k =&gt; Additive (Map k) where 的声明让我认为Map Int b 应该是任何b 的附加值。但我明白我为什么错了。 Haskell 语法对我来说仍然有点陌生,所以我花了一段时间才明白它实际上在做什么,这非常优雅。感谢您指出!
  • 其实答案只是部分令人满意。我确实可以通过使用^+^ 来添加Map k Int,但我不能添加功能a -&gt; Map k Int,而适当的电梯会授权我这样做。你有什么解决办法吗?
猜你喜欢
  • 1970-01-01
  • 2016-11-25
  • 2013-12-25
  • 1970-01-01
  • 2019-04-05
  • 1970-01-01
  • 2016-02-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多