你可以在Haskell中重载+吗?
虽然我看到了Haskell示例代码中的各种奇怪的东西 – 我从来没有见过一个运算符加上被重载。 有什么特别的吗?
假设我有一个像Pair这样的types,我想要类似的东西
Pair(2,4) + Pair(1,2) = Pair(3,6)
哈斯克尔能做到吗?
我只是好奇,因为我知道它可能在斯卡拉相当优雅的方式。
是
(+)
是Num
types类的一部分,每个人似乎都觉得你不能为你的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类。 在这种情况下, (+)
属于Num
types,所以你必须为你的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")