考虑以下类型,它描述了一些二维形状的结构:
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类型中。让他们成为不同的类型。如上所述,在不同情况下需要两种属性。 有人对此有建议吗?是一种 - 或者,或者是否有一种方法来模拟它利用两种方式?
答案 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,它使用非常复杂的图片模型,涉及过多的类型和类,并且以类似的方式处理诸如缩放之类的操作。