在Haskell中,如何覆盖类型类的(==)和(/ =)运算符?

时间:2017-11-02 11:35:55

标签: haskell operator-overloading

说我有类似的东西

class Circle c where
    x :: c -> Float
    y :: c -> Float
    radius :: c -> Float

data Location = Location { locationX :: Float
                         , locationY :: Float
                         } deriving (Show, Eq)

data Blob = Location { blobX :: Float
                     , blobY :: Float
                     , blobRadius :: Float,
                     , blobRating :: Int
                     } deriving (Show, Eq)

instance Circle Location where
    x = locationX
    y = locationY
    radius = pure 0

instance Circle Blob where
    x = blobX
    y = blobY
    radius = blobRadius

比方说,如果他们的x和y点相等,我希望Circle类型相等。如何将类型类的实例与(==)和(/ =)运算符进行比较。我知道我可以做这样的事情,但是可以超载操作员吗?

equal :: Circle a => Circle b => a -> b -> Bool
equal a b = (x a == x b && y a == y b)

我希望能够与

进行比较

(Location 5.0 5.0) == (Blob 5.0 5.0 ... )应该给我真实的

2 个答案:

答案 0 :(得分:7)

Zeroth ,一些标准导入:

import Data.Function (on)
import Control.Arrow ((&&&))

<小时/> 首先,这不是一个好主意。只有当a==ba(对于与用户相关的所有目的)可互换时,b才应该是真的 - 对于两个只是碰巧共享相同中心点的圆圈来说显然不是这种情况!

第二次,首先让Circle成为类型类别可能不是一个好主意。只有当你想要抽象不能仅用参数直接表达的东西时,类型类才有意义。但是,如果你只是想将不同的“有效载荷”附加到空间中的点,那么更明智的方法可能是定义像

这样的东西。
data Located a = Located {x,y :: ℝ, payload :: a}

如果在某种情况下,您实际上希望允许不同的Circle 实例共存并在运行时具有可比性,那么类型类完全是错误的选择。这将是一个OO类。 Haskell没有任何内置的概念,但你可以使用

data Blob = Blob
   { x,y :: ℝ
   , radius :: ℝ
   , rating :: Maybe Int }

并没有其他类型。

https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/

第三次,您要求的实例可以理论上说,定义为

instance (Circle a) => Eq a where
  (==) = (==)`on`(x &&& y)

但这将是一个非常可怕的想法。这将是一个包罗万象的实例:无论何时比较任何,编译器都会检查“它的格式是a吗?”(字面意思任何是那个形式)“哦,很好,然后说实例告诉我如何比较这个。”只有稍后才会看到Circle要求。

正确解决方案是根本不定义任何此类Eq实例。你的类型已经单独拥有Eq个实例,这通常应该是正确的使用 - 根本不需要通过Circle类来表达它,只需给出需要做的任何函数来比较约束(Circle a, Eq a) => ...

当然,这些实例然后只是比较位置而是整个数据,正如我所说,这是一件好事。如果你真的想只比较结构的一部分,那么,明确说明!不要使用==本身,而是提取相关部分并进行比较。对此有用的帮助可能是

location :: Circle a => a -> Location
location c = Location (x c) (y c)

...然后,对于任何Circle类型,您只需编写(==)`on`location而不是(==),即可忽略除该位置之外的任何其他信息。或者直接写出(==)`on`(x &&& y),这可以很容易地调整到其他情况。

答案 1 :(得分:2)

共享一个共同中心的两个圆圈不一定相等,但它们是同心;那就是你应该写一个要检查的函数。

concentric :: (Circle a, Circle b) => a -> b -> Bool
concentric c1 c2 = x c1 == x c2 && y c1 == y c2