展开Haskell数据类型

时间:2012-01-15 13:55:21

标签: haskell types

是否可以使用新值扩展数据类型?

例如为: 以下编译:

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类型也可以吗? 如果有可能:我做错了什么?另外:为什么不可能?

3 个答案:

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

这有许多与子类型相同的缺点:axisXaxisYaxisZ的使用会变得模棱两可,需要类型注释才能破坏练习。与使用具体类型相比,使用这些类型约束编写类型签名也是一件有用的事情。

还有另一个缺点:对于具体类型,当你编写一个Axes2D的函数时,一旦你处理XY,你知道你已经涵盖了所有可能的值。使用类型类解决方案,没有什么能阻止您将Z传递给期望HasAxes2D实例的函数。您真正想要的是让关系反过来,以便您可以将XY传递给期望3D轴的函数,但无法将Z传递给函数期待2D轴。我认为没有办法用Haskell的类型系统正确建模。

这种技术偶尔会有用 - 例如,将像GUI工具包这样的OOP库绑定到Haskell - 但一般来说,使用具体类型并明确支持在OOP术语中称为composition over inheritance的内容更为自然,即在构造函数中显式包装“子类型”。处理构造函数包装/解包通常不是很麻烦,而且它更灵活。

答案 1 :(得分:8)

这是不可能的。请注意

data Axes2D = X | Y
data Axes3D = Axes2D | Z

Axes2D类型中的Axes3D是一个不带参数的值构造函数,因此Axes3D有两个构造函数,Axes2DZ

不同的类型不能具有相同名称的值构造函数(在同一范围内),因为这会使类型推断变得不可能。什么会

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)))

现在,您可以保证只将XY传递给定义为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