来自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?
那么为什么选择这个呢?这是遗产还是没有人想要这个?
答案 0 :(得分:3)
很多是由于历史原因,但也有数学原因。例如,已经有支持二元运算符的结构的数学名称。最常用的是Monoid,可以在Data.Monoid
中使用。此类型类定义函数mappend
和值mempty
,相当于mappend
的标识元素,并且mappend
有一个名为<>
的运算符别名。列表和许多其他对象形成幺半群,数字实际上形成2,+
和*
,其中标识元素分别为0和1。具有标识,关联二元运算和该运算的逆的结构(例如,减法是加法的逆)被称为组(不是标准库的一部分),以及在一个运算符下形成组的结构第二个运算符下的幺半群称为环。这些对象是代数结构/抽象代数类的基础。
这些数学结构在Haskell中实现起来有点棘手,至少非常好。对于Monoid
和Num
的所有+
类型,*
都有重叠的实例,而对于某些数字类型,如果Group
则会/
重叠可以定义为明确划分0(某些结构可以允许这样)。这些重叠的实例导致许多新类型,使其难以在日常工作。 Num
类型类在这里有所帮助,因为它提供了一个有用的界面,即操作和执行数字操作,这在现实世界的代码中很容易使用,而不仅仅是在学术界。有人尝试引入Prelude
的更多数学版本,有些人使用这些版本取得了不同的成功,但是你的普通Haskeller宁愿放弃数学纯度以获得更实用的界面。
简而言之,Num
类型类由于历史原因而以这种方式定义,但也是出于非常实际的原因。更严格的数学结构是不实用的,对于许多类型而言,只使用Data.Monoid
的{{1}}运算符是完美的。
答案 1 :(得分:1)
我的猜测是他们打算Num
反映数字的数学性质,而不是一些更一般的结构。 Num
中的操作主要是支持称为 ring 的抽象数学结构的操作,其中整数(及其任何扩展)是一个突出的例子。
由于Haskell允许您定义自己的运算符,因此您可以设置自己的表示法。然后,当其他人阅读您的代码时,他们将能够告诉您正在使用的东西不是Num
类似的,但可能更为通用。
在Haskell前奏中有许多不幸的历史设计决定,这些决定无法修复,因为它们会破坏兼容性。但是,恕我直言,Num
类型类和运算符的基本设计是合理的。