在超类函数的定义中使用子类实现



在我的haskell程序中,我有一些类型类,代表"形状"的抽象概念,即

-- | Class representing shapes.
class Shape a where
  isColliding :: (Shape b) => a -> b -> Bool
  centroid :: Point
-- | Class representing shapes composed of a finite number of vertices 
and line segments connecting them.
class (Shape a) => Polygon a where
  vertices :: a -> Vertices 

如您所见,Polygon自然是Shape的子类。我也有一些数据类型是这些不同类型的实例。例如:

data Box = Box Point Point Angle
instance Shape Box where
  ...
instance Polygon Box where
  ...
---------------------------------
data Circle = Circle Point Radius
instance Shape Circle where
  ...

我还有更多可能的形状,例如NGonRegularNGon等。我希望能够实现isColliding,但是计算是否有两个形状碰撞所需的信息取决于Shape的特定实例的实现。例如,要计算两个框是否碰撞,我需要他们的顶点列表。所以我有几个问题:

  1. 无论如何是否有"专业化"我的函数 isColliding,以便以特定方式定义 isColliding :: (Polygon b) => Box -> b -> Bool类型的碰撞?
  2. 我的数据类型的结构是解决此问题的最佳方法,还是当整个过程可以进行重组以消除此问题时,我是否会滥用类型和数据类型?

我是Haskell的新手,因此,如果我的问题措辞不佳或需要任何澄清,请告诉我。

您的当前Shape班级说" isColliding可以告诉另一种形状是否仅使用Shape 在另一个形状上的方法相交",因为仅使用其签名(Shape b) => a -> b -> Bool告诉您b的实例为Shape。因此,您是对的,这不是您想要的。

您可以做的一件事是使用MultiParamTypeClasses描述两种类型之间的A 关系

{-# LANGUAGE MultiParamTypeClasses #-}
class Colliding a b where
  collidesWith :: a -> b -> Bool

,然后制作各种混凝土组合的实例:

instance Colliding Circle Box where
  Circle p r `collidesWith` Box p1 p2 θ = {- … -}

在这里,您知道定义实现时ab的具体类型。对于您的用例,这可能足够好。

但是,如果您具有 n 类型,这使您拥有 n 2 实例。如果您尝试定义这样的多态实例,那么您会遇到问题:

instance (HasBoundingBox b) => Colliding Circle b where
  collidesWith = {- … -}

因为此重叠在您的所有其他实例中,Colliding Circleb都将匹配任何类型,并且仅添加b必须具有HasBoundingBox实例的约束。在实例分辨率之后检查该约束。您可以使用OverlappingInstances或更新的OVERLAPPABLE/OVERLAPPING/OVERLAPS PRAGMAS来解决此问题,以告诉GHC选择最具体的匹配实例,但这可能比您熟悉的情况更值得与Haskell。

我必须更多地考虑一下,但是肯定有其他方法。在最简单的情况下,如果您只需要处理几种不同类型的形状,那么您只需将它们制成单一总和而不是单独的数据类型:

data Shape
  = Circle Point Radius
  | Box Point Point Angle
  | …

那么您的isColliding功能可以是类型的Shape -> Shape -> Bool,并且仅在此类型上进行模式匹配。

一般来说,如果您正在编写类型阶级,则应随着实例的行为方式,例如Data.Monoid中的mappend x mempty == mappend mempty x == x。如果您不能想到应该始终在班级实例中保留的任何方程式,那么您应该更喜欢用普通的旧功能和数据类型来表示事物。

最新更新