[英]How choose which classes to make an instance of when overloading operators in haskell?
我目前正在尝试使基本数据运算符(+,-,*,/,取反)和排序运算符(<,>,> =,<=,==)重载为组合数据类型Pair。 当前,我有一个num实例,要求我重载它的可见成员,但是我很难弄清楚是否需要制作不同类(例如Fractional,Ord等)的多个实例,或者是否有更好的实例重载这些特定运算符的方法。
代码示例:
data Pair = Pair (Int, Int) deriving (Eq, Show)
instance Num Pair where
Pair (a,b) + Pair (c,d) = Pair (a+c,b+d)
Pair (a,b) * Pair (c,d) = Pair (a*c,b*d)
Pair (a,b) - Pair (c,d) = Pair (a-c,b-d)
Pair (a,b) / Pair (c,d) = Pair (a/c , b/d) // throws an error for this line.
abs (Pair (a,b)) = Pair (abs a, abs b)
signum (Pair (a,b)) = Pair (signum a, signum b)
fromInteger i = Pair (fromInteger i, fromInteger i)
通常,在Haskell中,您应避免考虑“使操作员超负荷”。 类型类不仅仅是重载运算符的语法; 宁可将它们视为数据类型的抽象公理 。 现在,如果这听起来很吓人,请忘掉公理一词并考虑一个例子。
数字类型 N
是一种类型,其值a
, b
, c
...满足属性( 定律 ),例如
a + 0 = a
a * 1 = a
a + (b + c) = (a + b) + c
abs a * signum a = a
正是这些属性使您可以真正编写num-polymorphic代码(即,可以与任何数字类型一起使用),并且仍然可以确保最终获得所有这些代码的有意义的结果。 这确实是超载的力量。 仅仅能够重用一个很好的短标识符(例如+
本身可能就很方便,但是与+.
这样的自定义运算符相比,您实际上并不会从中受益+.
用于特殊的num类型( O'Caml就是这样做的 )。
现在,要使这样的法律得以制定,就需要使所有这些运算符可用,这就是为什么我们有一些类要求我们不仅要重载我们现在喜欢的任何一个运算符,而且要重载它们的整个相关类。
类不是一些特殊的内置魔术。 您可以轻松定义自己的类† ,或者对于Num
类的标准类,查找它们的定义位置。 查找类定义(或任何其他库声明)的最简单方法是Hayoo seatch engine 。 例如,它将+
引导您立即访问(+) :: a -> a -> a
a- (+) :: a -> a -> a
a- (+) :: a -> a -> a
hackage (+) :: a -> a -> a
类方法 。 在那里,您可以看到+
包含在其中
class Num a where
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
(+), (-), (*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
因此,如果要重载+
运算符,则需要定义(+)
, (-)
, (*)
, negate
, abs
, signum
和fromInteger
所有‡ 。
abs
! 很合理 但是,您的类型显然不是数字类型! IMO,您不应将任何事物都做成Num
实例,而不仅仅是一个数字的表示形式。 请注意,有一些更简单的类可用:您可能想要AdditiveGroup
class AdditiveGroup v where
zeroV :: v
(^+^) :: v -> v -> v
negateV :: v -> v
(^-^) :: v -> v -> v
这是用于更一般的向量空间的类 。 您可以轻松地将类型设为实例:
instance AdditiveGroup Pair where
zeroV = Pair (0,0)
Pair (a,b) ^+^ Pair (c,d) = Pair (a+c, b+d)
Pair (a,b) ^-^ Pair (c,d) = Pair (a-c, b-d)
negateV (Pair (a,b)) = Pair (-a ,-b)
您会注意到该类没有乘法。 好吧,事实证明,在数学上,乘法对元组并没有多大意义。 但是,还有其他种类的乘法(标量乘法)在向量空间上有意义。 浏览vector-space软件包的文档 。
还要考虑您是否甚至需要AdditiveGroup实例。 丹尼尔·瓦格纳(Daniel Wagner) 在回答中使用了比数字更通用的类,并且可用于各种容器类型。 可以肯定的是,您的Pair
更是数字的容器 ,而不是数字本身。 因此,我建议仅实施
data Pair a = Pair a a
instance Functor Pair where
fmap f (Pair x y) = Pair (f x) (f y)
instance Applicative Pair where
pure v = Pair v v
Pair f g <*> Pair x y = Pair (f x) (g y)
Functor
非常简单:它允许您将函数应用于容器中的所有元素。 基本上,“ Applicative
使您可以在两个容器之间交叉应用功能。
在这些实例中,您可以直接使用liftA2 (+) (Pair 1 2) (Pair 3 4)
添加对,而不必直接使用数字运算符。 更加冗长,但也更加明确!
† 坦白地说,最好不要经常这样做。 我说过的课程代表着深层的数学公理。 对于OO程序员要为其编写类的任何事情,这都是不可能的。 也不总是需要编写自定义类:仅凭data
就可以使您用一种功能语言走得很远。
‡ 其实这是足以实现任何 (-)
或 negate
。 这就是MINIMAL
指示告诉您的。
我想我可能会写这样的东西:
{-# LANGUAGE DeriveFunctor #-}
import Control.Applicative
data Pair a = Pair a a deriving (Eq, Ord, Read, Show, Functor)
instance Applicative Pair where
pure v = Pair v v
Pair f g <*> Pair x y = Pair (f x) (g y)
instance Num a => Num (Pair a) where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
negate = liftA negate
abs = liftA abs
signum = liftA signum
fromInteger = pure . fromInteger
instance Fractional a => Fractional (Pair a) where
(/) = liftA2 (/)
recip = liftA recip
fromRational = pure . fromRational
您可以通过查看其类型来了解特定操作与哪个类相关:
(/) :: Fractional a => a -> a -> a
因此,如果您想支持(/)
,则应考虑实现Fractional
类。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.