Haskell:为什么(+),( - )是Num类型类的一部分?

时间:2016-01-13 02:40:55

标签: haskell typeclass

来自ghci:

Prelude> :i Num                              
class Num a where                            
  (+) :: a -> a -> a                         
  (-) :: a -> a -> a                         
  (*) :: a -> a -> a                         
  negate :: a -> a                           
  abs :: a -> a                              
  signum :: a -> a                           
  fromInteger :: Integer -> a                
        -- Defined in `GHC.Num'              
instance Num Word -- Defined in `GHC.Num'    
instance Num Integer -- Defined in `GHC.Num' 
instance Num Int -- Defined in `GHC.Num'     
instance Num Float -- Defined in `GHC.Float' 
instance Num Double -- Defined in `GHC.Float'

为什么开始使用(+)(-) Num类的一部分?

例如 - 您可以轻松定义此类型类:

class Plus a where
     (+) :: a -> a -> a

然后让:

instance Plus [] where
    (+) = (++)

你也可以为集合定义这些集合来表示集合,或者将(-)添加到类型类来表示集合差异......在列表中定义signum是没有意义的。

当然,我可以使用(|+|)创建自己的类型类   - 但是为什么这些运算符只在Nank中用于Num?

那么为什么选择这个呢?这是遗产还是没有人想要这个?

2 个答案:

答案 0 :(得分:3)

很多是由于历史原因,但也有数学原因。例如,已经有支持二元运算符的结构的数学名称。最常用的是Monoid,可以在Data.Monoid中使用。此类型类定义函数mappend和值mempty,相当于mappend的标识元素,并且mappend有一个名为<>的运算符别名。列表和许多其他对象形成幺半群,数字实际上形成2,+*,其中标识元素分别为0和1。具有标识,关联二元运算和该运算的逆的结构(例如,减法是加法的逆)被称为组(不是标准库的一部分),以及在一个运算符下形成组的结构第二个运算符下的幺半群称为环。这些对象是代数结构/抽象代数类的基础。

这些数学结构在Haskell中实现起来有点棘手,至少非常好。对于MonoidNum的所有+类型,*都有重叠的实例,而对于某些数字类型,如果Group则会/重叠可以定义为明确划分0(某些结构可以允许这样)。这些重叠的实例导致许多新类型,使其难以在日常工作。 Num类型类在这里有所帮助,因为它提供了一个有用的界面,即操作和执行数字操作,这在现实世界的代码中很容易使用,而不仅仅是在学术界。有人尝试引入Prelude的更多数学版本,有些人使用这些版本取得了不同的成功,但是你的普通Haskeller宁愿放弃数学纯度以获得更实用的界面。

简而言之,Num类型类由于历史原因而以这种方式定义,但也是出于非常实际的原因。更严格的数学结构是不实用的,对于许多类型而言,只使用Data.Monoid的{​​{1}}运算符是完美的。

答案 1 :(得分:1)

我的猜测是他们打算Num反映数字的数学性质,而不是一些更一般的结构。 Num中的操作主要是支持称为 ring 的抽象数学结构的操作,其中整数(及其任何扩展)是一个突出的例子。

由于Haskell允许您定义自己的运算符,因此您可以设置自己的表示法。然后,当其他人阅读您的代码时,他们将能够告诉您正在使用的东西不是Num类似的,但可能更为通用。

在Haskell前奏中有许多不幸的历史设计决定,这些决定无法修复,因为它们会破坏兼容性。但是,恕我直言,Num类型类和运算符的基本设计是合理的。