通过类型系统表达备用功能集

时间:2012-07-17 18:40:18

标签: haskell

我有一个类型类Shape,它声明了所有形状共有的许多函数。其中一个函数(refine)需要返回子形状列表。为了表达这种约束,我使用了存在量化:

data Shapeable = forall a . Shape a => Shapeable a

并让函数返回[Shapeable]。我有一个额外的约束,可以改进某些形状(通过refine函数),而其他形状可以检查交集(通过intersect函数)。这些是相互排斥的,因为可以自我修饰的形状不能检查交叉,反之亦然。

如果我没有使用量化,我会创建另外两个类型:IntersectableRefineable。有没有办法在像系统这样的单个类类中表达不相交的函数集?

3 个答案:

答案 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

如果它具有交集,则该函数可以将形状传递给第一个延续,如果它具有细化,则该函数可以传递给第二个延续。你可以想到各种结合方式。使用MonoidAlternative,您甚至可以同时执行这两项操作:

newShape ::
    (Alternative f) =>
    (forall a. (a -> a) -> a -> f b) ->
    (forall a. (a -> a) -> a -> f b) ->
    ShapeConfig ->
    f b

使用RankNTypes的优势在于您可以编写更灵活的函数。您现在可以使用折叠,地图或任何您想要的任何内容,而不是简单的构造函数。