你可以在Haskell中重载+吗?

虽然我看到了Haskell示例代码中的各种奇怪的东西 – 我从来没有见过一个运算符加上被重载。 有什么特别的吗?

假设我有一个像Pair这样的types,我想要类似的东西

Pair(2,4) + Pair(1,2) = Pair(3,6) 

哈斯克尔能做到吗?

我只是好奇,因为我知道它可能在斯卡拉相当优雅的方式。

(+)Numtypes类的一部分,每个人似乎都觉得你不能为你的types定义(*)等,但我强烈反对。

 newtype Pair ab = Pair (a,b) deriving (Eq,Show) 

我认为双Pair ab会更好,或者我们甚至可以直接使用types(a,b) ,但是…

这非常像两个幺半群,组,环或者math中的任何东西的笛卡尔乘积,并且有一个定义数字结构的标准方法,这将是明智的使用。

 instance (Num a,Num b) => Num (Pair ab) 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 (ac,bd) 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) 

现在我们以一种明显的方式重载了(+) ,但是同样的,明显的,熟悉的方式也使整个猪和重载(*)以及所有其他的Num函数变成了一对。 我只是没有看到这个问题。 其实我觉得这是很好的做法。

 *Main> Pair (3,4.0) + Pair (7, 10.5) Pair (10,14.5) *Main> Pair (3,4.0) + 1 -- * Pair (4,5.0) 

* – 请注意,从Pair (1,1.0) :: Pair Integer Double应用于数字文字(如1 ,所以在上下文中解释为Pair (1,1.0) :: Pair Integer Double 。 这也相当不错,方便。

在Haskell中重载只能使用types类。 在这种情况下, (+)属于Numtypes,所以你必须为你的types提供一个Num实例。

但是, Num还包含其他函数,一个行为良好的实例应该以一致的方式实现所有这些函数,除非您的types代表某种types的数字,否则通常不会有任何意义。

所以除非是这样,否则我会build议定义一个新的操作符。 例如,

 data Pair ab = Pair ab deriving Show infixl 6 |+| -- optional; set same precedence and associativity as + Pair ab |+| Pair cd = Pair (a+c) (b+d) 

您可以像使用其他操作符一样使用它:

 > Pair 2 4 |+| Pair 1 2 Pair 3 6 

我会很直接地试着回答这个问题,因为你急于在超载(+)上得到一个“是或否”的结果。 答案是肯定的,你可以重载它。 有两种方法直接重载它,没有任何其他的改变,以及一种“正确”重载它的方式,这需要为你的数据types创build一个Num实例。 在其他答案中详细说明了正确的方法,所以我不会去理解它。

编辑:请注意,我不推荐以下讨论的方式,只是logging它。 你应该实现Numtypes类,而不是我写在这里的任何东西。

重载(+)的第一个(也是最“错误的”)方法就是简单地隐藏Prelude。+函数,并定义自己的名为(+)的函数来处理你的数据types。

 import Prelude hiding ((+)) -- hide the autoimport of + import qualified Prelude as P -- allow us to refer to Prelude functions with a P prefix data Pair a = Pair (a,a) (+) :: Num a => Pair a -> Pair a -> Pair a -- redefinition of (+) (Pair (a,b)) + (Pair (c,d)) = Pair ((P.+) ac,(P.+) bd ) -- using qualified (+) from Prelude 

你可以在这里看到,我们必须通过一些扭曲来隐藏导入的(+)的常规定义,但是我们仍然需要一种方法来引用它,因为它是实现快速机器添加的唯一方法(这是一个原始操作)。

第二个(稍微错误)的方法是定义你自己的typestypes,只包含一个新的操作符(+)。 你仍然必须隐藏旧的(+),所以haskell不会感到困惑。

 import Prelude hiding ((+)) import qualified Prelude as P data Pair a = Pair (a,a) class Addable a where (+) :: a -> a -> a instance Num a => Addable (Pair a) where (Pair (a,b)) + (Pair (c,d)) = Pair ((P.+) ac,(P.+) bd ) 

这比第一个选项好一点,因为它允许你在你的代码中使用新的(+)来处理许多不同的数据types。

但是,这些都不推荐,因为正如你所看到的,访问在Numtypes类中定义的常规(+)运算符是非常不方便的。 即使haskell允许你重新定义(+),所有的Prelude和库都期待着原来的(+)定义。 幸运的是,(+)是在types类中定义的,所以你可以让Pair成为Num的一个实例。 这可能是最好的select,这是其他回答者推荐的。

你遇到的问题是在Numtypes类中定义的函数可能太多(+是其中之一)。 这只是一个历史的事故,现在使用Num的情况非常普遍,现在很难改变。 而不是将这些function拆分为每个function的单独的typestypes(所以它们可以分开重写),它们都被一起绑定。 理想情况下,Prelude将有一个Addabletypestypes和一个Subtractabletypes类,允许您一次为一个运算符定义一个实例,而不必实现Num在其中的所有内容。

尽pipe如此,事实是如果你想为你的Pair数据types写一个新的(+),你将会打一场艰苦的战斗。 其他Haskell代码太多取决于Numtypestypes及其当前定义。

如果您正在寻找试图避免当前错误的Prelude的蓝天重新实现,您可以看看数字序曲 。 你会注意到他们已经将Prelude作为一个库重新实现,没有编译器的黑客攻击是必要的,尽pipe这是一个巨大的任务。

Haskell中的重载是通过types类来实现的。 为了一个好的概述,你可能想看看这一节学习你一个Haskell 。

(+)运算符是Prelude中 Num类的一部分:

 class (Eq a, Show a) => Num a where (+), (*), (-) :: a -> a -> a negate :: a -> a ... 

所以如果你想为+定义+对,你将不得不提供一个实例。

如果你有一个types:

 data Pair a = Pair (a, a) deriving (Show, Eq) 

那么你可能有一个像这样的定义:

 instance Num a => Num (Pair a) where Pair (x, y) + Pair (u, v) = Pair (x+u, y+v) ... 

冲这个ghci给我们:

 *Main> Pair (1, 2) + Pair (3, 4) Pair (4,6) 

然而,如果你打算给一个+的实例,你也应该为这个类的所有其他函数提供一个实例,这可能并不总是有意义的。

如果你只想要(+)运算符而不是所有的Num运算符,可能你有一个Monoid实例,例如Monoid对的实例是这样的:

 class (Monoid a, Monoid b) => Monoid (a, b) where mempty = (mempty, mempty) (a1, b1) `mappend` (a2, b2) = (a1 `mappend` a2, b1 `mappend` b2) 

你可以使(++)mappend的别名,然后你可以这样写代码:

 (1,2) ++ (3,4) == (4,6) ("hel", "wor") ++ ("lo", "ld") == ("hello", "world")