在我的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
...
我还有更多可能的形状,例如NGon
,RegularNGon
等。我希望能够实现isColliding
,但是计算是否有两个形状碰撞所需的信息取决于Shape
的特定实例的实现。例如,要计算两个框是否碰撞,我需要他们的顶点列表。所以我有几个问题:
- 无论如何是否有"专业化"我的函数
isColliding
,以便以特定方式定义isColliding :: (Polygon b) => Box -> b -> Bool
类型的碰撞? - 我的数据类型的结构是解决此问题的最佳方法,还是当整个过程可以进行重组以消除此问题时,我是否会滥用类型和数据类型?
我是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 θ = {- … -}
在这里,您知道定义实现时a
和b
的具体类型。对于您的用例,这可能足够好。
但是,如果您具有 n 类型,这使您拥有 n 2 实例。如果您尝试定义这样的多态实例,那么您会遇到问题:
instance (HasBoundingBox b) => Colliding Circle b where
collidesWith = {- … -}
因为此重叠在您的所有其他实例中,Colliding Circle
:b
都将匹配任何类型,并且仅添加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
。如果您不能想到应该始终在班级实例中保留的任何方程式,那么您应该更喜欢用普通的旧功能和数据类型来表示事物。