是否可以使用新值扩展数据类型?
例如为: 以下编译:
data Axes2D = X | Y
data Axes3D = Axes2D | Z
但是,以下内容:
data Axes2D = X | Y deriving (Show, Eq)
data Axes3D = Axes2D | Z deriving (Show, Eq)
type Point2D = (Int, Int)
type Point3D = (Int, Int, Int)
move_along_axis_2D :: Point2D -> Axes2D -> Int -> Point2D
move_along_axis_2D (x, y) axis move | axis == X = (x + move, y)
| otherwise = (x, y + move)
move_along_axis_3D :: Point3D -> Axes3D -> Int -> Point3D
move_along_axis_3D (x, y, z) axis move | axis == X = (x + move, y, z)
| axis == y = (x, y + move, z)
| otherwise = (x, y, z + move)
给出以下编译错误(move_along_axis_3D
注释掉不会出错:
Prelude> :l expandTypes_test.hs
[1 of 1] Compiling Main ( expandTypes_test.hs, interpreted )
expandTypes_test.hs:12:50:
Couldn't match expected type `Axes3D' with actual type `Axes2D'
In the second argument of `(==)', namely `X'
In the expression: axis == X
In a stmt of a pattern guard for
an equation for `move_along_axis_3D':
axis == X
Failed, modules loaded: none.
那么类型X
的{{1}}和Y
以及Axes2D
类型也可以吗?
如果有可能:我做错了什么?另外:为什么不可能?
答案 0 :(得分:10)
与Daniel Fischer所说的一样,为了扩展为什么这是不可能的:你想要的那种子类型的问题比命名模糊性更深入;一般来说,他们使类型推理变得更加困难。我认为由于这个原因,Scala的类型推断比Haskell更受限制和局部化。
但是,您可以使用类型系统对此类事物进行建模:
class (Eq t) => HasAxes2D t where
axisX :: t
axisY :: t
class (HasAxes2D t) => HasAxes3D t where
axisZ :: t
data Axes2D = X | Y deriving (Eq, Show)
data Axes3D = TwoD Axes2D | Z deriving (Eq, Show)
instance HasAxes2D Axes2D where
axisX = X
axisY = Y
instance HasAxes2D Axes3D where
axisX = TwoD X
axisY = TwoD Y
instance HasAxes3D Axes3D where
axisZ = Z
然后,您可以使用警卫对这些值进行“模式匹配”:
displayAxis :: (HasAxes2D t) => t -> String
displayAxis axis
| axis == axisX = "X"
| axis == axisY = "Y"
| otherwise = "Unknown"
这有许多与子类型相同的缺点:axisX
,axisY
和axisZ
的使用会变得模棱两可,需要类型注释才能破坏练习。与使用具体类型相比,使用这些类型约束编写类型签名也是一件有用的事情。
还有另一个缺点:对于具体类型,当你编写一个Axes2D
的函数时,一旦你处理X
和Y
,你知道你已经涵盖了所有可能的值。使用类型类解决方案,没有什么能阻止您将Z
传递给期望HasAxes2D
实例的函数。您真正想要的是让关系反过来,以便您可以将X
和Y
传递给期望3D轴的函数,但无法将Z
传递给函数期待2D轴。我认为没有办法用Haskell的类型系统正确建模。
这种技术偶尔会有用 - 例如,将像GUI工具包这样的OOP库绑定到Haskell - 但一般来说,使用具体类型并明确支持在OOP术语中称为composition over inheritance的内容更为自然,即在构造函数中显式包装“子类型”。处理构造函数包装/解包通常不是很麻烦,而且它更灵活。
答案 1 :(得分:8)
这是不可能的。请注意
data Axes2D = X | Y
data Axes3D = Axes2D | Z
Axes2D
类型中的Axes3D
是一个不带参数的值构造函数,因此Axes3D
有两个构造函数,Axes2D
和Z
。
不同的类型不能具有相同名称的值构造函数(在同一范围内),因为这会使类型推断变得不可能。什么会
foo X = True
foo _ = False
作为一种类型? (它与参数类型有点不同,所有Maybe a
都具有相同名称的值构造函数,并且可行。但这是因为Maybe
采用了类型参数,并且名称仅在使用相同的(一元)类型构造函数。它不适用于nullary类型构造函数。)
答案 2 :(得分:2)
您可以使用广义代数数据类型。我们可以使用具有类型约束的数据构造函数创建泛型(GADT)类型。然后我们可以定义指定完整类型的特殊类型(类型别名),从而限制允许哪些构造函数。
{-# LANGUAGE GADTs #-}
data Zero
data Succ a
data Axis a where
X :: Axis (Succ a)
Y :: Axis (Succ (Succ a))
Z :: Axis (Succ (Succ (Succ a)))
type Axis2D = Axis (Succ (Succ Zero))
type Axis3D = Axis (Succ (Succ (Succ Zero)))
现在,您可以保证只将X
和Y
传递给定义为Axis2D
参数的函数。构造函数Z
无法匹配Axis2D
的类型。
不幸的是,GADT不支持自动deriving
,因此您需要提供自己的实例,例如:
instance Show (Axis a) where
show X = "X"
show Y = "Y"
show Z = "Z"
instance Eq (Axis a) where
X == X = True
Y == Y = True
Z == Z = True
_ == _ = False