具有多种数据构造函数与多种类型的单一类型

时间:2016-11-22 10:11:57

标签: haskell

考虑以下类型,它描述了一些二维形状的结构:

data DrawingElem
    = Rect     Pos Size
    | Circle   Pos Radius
    | Ellipse  Pos Radius Radius
    | Line     Pos Pos
    | Polygon  [Pos]
    | Polyline [Pos]

    | Group    [DrawingElem]
    | Drawing  [DrawingElem]

使用这些定义:

data Vec    = Vec Double Double

type Pos    = Vec
type Size   = Vec
type Radius = Double

DrawingElem的最后两个数据构造函数在某种程度上是特殊的,因为它们可以使其他类型的树状排列成为可能。

mydrawing = Drawing [Rect (Vec 0 0) (Vec 10 10),
                     Group (Vec 30 40) [Circle (Vec 0 0) 90,
                                        Line (Vec 0 0) (Vec 50 50)]]

这样的数据结构最终应该转换为可渲染的SVG-String:

toSvg :: DrawingElem -> String
toSvg (Drawing xs)               = "<svg>" ++ concatMap toSvg xs ++ "</svg>"
toSvg (Group xs)                 = "<g>" ++ concatMap toSvg xs ++ "</g>"
toSvg (Rect (Vec x y) (Vec w h)) = "<rect x='" ++ x ... "</rect>"

为此,我认为有必要在DrawingElem类型中包装不同的形状。它们必须具有相同的类型才能嵌套并最终呈现。

在其他一些场合,我希望它们是不同的类型:对于设置矩形大小的函数说(这只对矩形有意义,其他的没有大小的概念) :

setSize :: Size -> Rect -> Rect

这当然不适用于上述定义,必须是:

setSize :: Size -> DrawingElem -> DrawingElem
setSize (Rect p s) = ..
setSize x          = x

所以我必须实现一个使该函数完整的通配符。但是,在没有出现类型错误的情况下编写setSize someSize someCircle对我来说是个问题。

所以最后我在努力将绘图元素包装在VS类型中。让他们成为不同的类型。如上所述,在不同情况下需要两种属性。 有人对此有建议吗?是一种 - 或者,或者是否有一种方法来模拟它利用两种方式?

2 个答案:

答案 0 :(得分:3)

一种选择是使用另一个间接层,并为每个元素提供精确的类型:

data DrawingElem
    = DERect     Rect
    | DECircle   Circle
    ...

data Rect   = Rect Pos Size
data Circle = Circle Pos Radius

toSvg :: DrawingElem -> String
...

setSize :: Size -> Rect -> Rect
...

作为一个小的缺点,我们需要模式匹配两个层,例如。

toSvg (DERect (Rect pos size)) = ...

更高级的替代方案可能是使用GADT。但是,这可能对你的任务来说太过分了。

{-# LANGUAGE GADTs, DataKinds #-}

data ElemType = Rect | Circle | ...

data DrawingElem (t :: ElemType) where
   DERect   :: Pos -> Size   -> DrawingElem Rect
   DECircle :: Pos -> Radius -> DrawingElem Circle
   ...

-- this works on all element types t
toSvg :: DrawingElem t -> String
...

-- this works only on a rectangle element
setSize :: Size -> DrawingElem Rect -> DrawingElem Rect
setSize size (DERect pos _) = DERect pos size

我不相信你是否真的需要这个。如果有疑问,请坚持使用更简单的替代方案。

答案 1 :(得分:2)

  

但是,在没有出现类型错误的情况下编写setSize someSize someCircle对我来说是个问题。

确实会有问题。为了避免这种情况,我将建议第三种选择:也许您根本不需要特定于矩形的setSize功能。另一种方法是保持单DrawingElem类型,在矩形构造上设置初始大小(以及圆构造的初始半径等),并使用可以为各种元素实现的函数来调整大小施工后,如:

scale :: Double -> DrawingElem -> DrawingElem

scaleX :: Double -> DrawingElem -> DrawingElem

scaleY :: Double -> DrawingElem -> DrawingElem

这与 gloss 处理形状的方式非常相似(参见the relevant type definition和一些picture manipulation functions)。值得一提的另一个例子是diagrams,它使用非常复杂的图片模型,涉及过多的类型和类,并且以类似的方式处理诸如缩放之类的操作。