考虑以下描述某些二维形状结构的类型:
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 字符串:
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 中。让他们成为不同的类型。如上所述,在不同情况下都需要这两个属性。 有人对此有建议吗?是非此即彼,还是有一种方法可以利用这两种方式对其进行建模?
最佳答案
一个选择是使用另一个间接层,并为每个元素指定一个精确的类型:
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
我不确定您是否真的需要这个。如有疑问,请坚持使用更简单的替代方案。
关于haskell - 具有多个数据构造函数的单一类型与多个类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40739071/