Haskell 你能超载吗+;在哈斯克尔?

Haskell 你能超载吗+;在哈斯克尔?,haskell,Haskell,虽然我在Haskell示例代码中看到了各种奇怪的事情,但我从未见过运算符plus被重载。有什么特别的吗 比如说我有一个像Pair这样的类型,我想要一个像Pair这样的类型 Pair(2,4) + Pair(1,2) = Pair(3,6) 在哈斯克尔能做到吗 我只是好奇,因为我知道它在Scala中以一种相当优雅的方式成为可能。Haskell中的重载是通过类型类实现的。要获得良好的概述,您可能需要查看 (+)运算符是来自的Num类型类的一部分: 因此,如果您希望+的定义能够成对使用,则必须提供

虽然我在Haskell示例代码中看到了各种奇怪的事情,但我从未见过运算符plus被重载。有什么特别的吗

比如说我有一个像Pair这样的类型,我想要一个像Pair这样的类型

 Pair(2,4) + Pair(1,2) = Pair(3,6)
在哈斯克尔能做到吗


我只是好奇,因为我知道它在Scala中以一种相当优雅的方式成为可能。

Haskell中的重载是通过类型类实现的。要获得良好的概述,您可能需要查看

(+)
运算符是来自的
Num
类型类的一部分:

因此,如果您希望
+
的定义能够成对使用,则必须提供一个实例

如果您有一个类型:

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)

但是,如果要为
+
提供一个实例,那么还应该为该类型类中的所有其他函数提供一个实例,这可能并不总是有意义的。

Haskell中的重载只能使用类型类。在这种情况下,
(+)
属于
Num
类型类,因此您必须为您的类型提供一个
Num
实例

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

因此,除非是这样,否则我建议定义一个新的操作符。比如说,

data Pair a b = Pair a b
    deriving Show

infixl 6 |+| -- optional; set same precedence and associativity as +
Pair a b |+| Pair c d = Pair (a+c) (b+d)
然后,您可以像其他任何操作员一样使用它:

> Pair 2 4 |+| Pair 1 2
Pair 3 6
如果您只需要
(+)
运算符而不是所有
Num
运算符,则可能有一个
Monoid
实例,例如pair的
Monoid
实例如下:

class (Monoid a, Monoid b) => Monoid (a, b) where
    mempty = (mempty, mempty)
    (a1, b1) `mappend` (a2, b2) = (a1 `mappend` a2, b1 `mappend` b2)
(1,2) ++ (3,4) == (4,6)
("hel", "wor") ++ ("lo", "ld") == ("hello", "world")
您可以将
(++)
设为
mappend
的别名,然后可以编写如下代码:

class (Monoid a, Monoid b) => Monoid (a, b) where
    mempty = (mempty, mempty)
    (a1, b1) `mappend` (a2, b2) = (a1 `mappend` a2, b1 `mappend` b2)
(1,2) ++ (3,4) == (4,6)
("hel", "wor") ++ ("lo", "ld") == ("hello", "world")

我将尝试非常直接地回答这个问题,因为您希望对重载(+)直接回答“是或否”。答案是肯定的,你可以超载。有两种方法可以直接重载它,而无需任何其他更改,还有一种方法可以“正确”重载它,这需要为您的数据类型创建Num实例。正确的方法在其他答案中有详细说明,所以我不想重复

编辑:请注意,我并不推荐下面讨论的方法,只是将其记录下来。您应该实现Num typeclass,而不是我在这里写的任何东西。

重载(+)的第一种(也是最“错误的”)方法是简单地隐藏Prelude.+函数,并定义自己的名为(+)的函数,该函数对数据类型进行操作

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.+) a c,(P.+) b d ) -- using qualified (+) from Prelude
您可以在这里看到,我们必须通过一些扭曲来隐藏(+)的常规定义,以防导入,但我们仍然需要一种方法来引用它,因为这是进行快速机器添加的唯一方法(这是一种基本操作)

第二种方法(错误稍微少一点)是定义自己的typeclass,它只包含一个您命名为(+)的新运算符。您仍然需要隐藏旧(+),以便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.+) a c,(P.+) b d )
这比第一个选项好一点,因为它允许您在代码中使用新的(+)来处理许多不同的数据类型

但是这两种方法都不推荐,因为正如您所看到的,访问Num typeclass中定义的常规(+)运算符非常不方便。尽管haskell允许您重新定义(+),但所有Prelude和库都需要原始(+)定义。幸运的是,(+)是在typeclass中定义的,因此您可以将Pair作为Num的实例。这可能是最好的选择,也是其他回答者推荐的

您遇到的问题是Num typeclass中定义的函数可能太多(+是其中之一)。这只是一个历史性的意外,现在Num的使用如此广泛,现在很难改变它。不是将这些功能拆分为每个功能的单独类型类(这样它们就可以被单独重写),而是将它们全部合并在一起。理想情况下,Prelude将有一个可添加的typeclass和一个可减去的typeclass等,允许您一次为一个运算符定义一个实例,而不必实现Num中的所有内容

尽管如此,如果您只想为Pair数据类型编写一个新(+),那么您将面临一场艰苦的战斗。太多其他Haskell代码依赖于Num typeclass及其当前定义

如果你正在寻找前奏曲的蓝天重演,试图避免当前前奏中的一些错误,那么你可以研究一下前奏。你会注意到,他们重新实现了Prelude,就像一个库一样,没有必要进行编译器黑客攻击,尽管这是一项巨大的任务。

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

newtype Pair a b = Pair (a,b)  deriving (Eq,Show) 
我认为a、b两个
组合会更好,或者我们甚至可以直接使用
(a、b)
类型,但是

这很像数学中两个幺半群、群、环或任何东西的笛卡尔积,有一种标准的方法来定义它的数字结构,使用起来很明智

instance (Num a,Num b) => Num (Pair a b) 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)
   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)
*
-请注意,
fromInteger
应用于数值文本,如
1
,因此在该上下文中,这被解释为
对(1,1.0)::Pair Integer Double
。这也很好用。

@drozzy你可以。令状