我有一个类型类Shape
,它声明了所有形状共有的许多函数。其中一个函数(refine
)需要返回子形状列表。为了表达这种约束,我使用了存在量化:
data Shapeable = forall a . Shape a => Shapeable a
并让函数返回[Shapeable]
。我有一个额外的约束,可以改进某些形状(通过refine
函数),而其他形状可以检查交集(通过intersect
函数)。这些是相互排斥的,因为可以自我修饰的形状不能检查交叉,反之亦然。
如果我没有使用量化,我会创建另外两个类型:Intersectable
和Refineable
。有没有办法在像系统这样的单个类类中表达不相交的函数集?
答案 0 :(得分:5)
我建议这样的事情:
data Shape
= Composite
{ refine :: [Shape]
, {- other type-class methods go here -}
}
| Primitive
{ intersect :: Shape -> Region
, {- other type-class methods go here -}
}
...并完全跳过类型类和存在量化。
答案 1 :(得分:2)
我相信你能得到的最接近的是两个存在的案例:
data Shapeable =
forall a . (Shape a, Intersectable a) => Intersectable a |
forall a . (Shape a, Refineable a) => Refineable a
答案 2 :(得分:1)
我建议不要使用类型类。将操作定义为简单数据类型:
data ShapeOps a =
ShapeOps {
intersect :: Maybe (a -> a),
refine :: Maybe (a -> a)
}
然后你可以使用存在量化:
data Shape =
forall a. Shape (ShapeOps a) a
这个概念更容易因素:
data Shape =
forall a.
Shape {
intersect :: Maybe (a -> a),
refine :: Maybe (a -> a),
shape :: a
}
使用Maybe
只是一个例子。您也可以使用 RankNTypes 而不是存在量化:
newShape ::
(forall a. (a -> a) -> a -> b) ->
(forall a. (a -> a) -> a -> b) ->
ShapeConfig ->
b
如果它具有交集,则该函数可以将形状传递给第一个延续,如果它具有细化,则该函数可以传递给第二个延续。你可以想到各种结合方式。使用Monoid
或Alternative
,您甚至可以同时执行这两项操作:
newShape ::
(Alternative f) =>
(forall a. (a -> a) -> a -> f b) ->
(forall a. (a -> a) -> a -> f b) ->
ShapeConfig ->
f b
使用RankNTypes的优势在于您可以编写更灵活的函数。您现在可以使用折叠,地图或任何您想要的任何内容,而不是简单的构造函数。