繁体   English   中英

在haskell中重载运算符时,如何选择要作为实例的类?

[英]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是一种类型,其值abc ...满足属性( 定律 ),例如

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

因此,如果要重载+运算符,则需要定义(+)(-)(*)negateabssignumfromInteger所有

哇,但也许您甚至都不想要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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM