简体   繁体   English

展开Haskell数据类型

[英]Expand Haskell datatypes

Is it possible to expand data types with new values? 是否可以使用新值扩展数据类型?

Eg: The following compiles: 例如:以下编译:

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

But, the following: 但是,以下内容:

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) 

gives the following compiling error ( move_along_axis_3D commented out doesn't give errors): 给出以下编译错误( 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.

So is it possible to make X and Y of type Axes2D as well of type Axes3D ? 那么Axes2D类型的XY是否也可以和Axes2D类型Axes3D If it is possible: what am I doing wrong? 如果有可能:我做错了什么? Else: why is it not possible? 另外:为什么不可能?

Along with what Daniel Fischer said, to expand on why this is not possible: the problems with the kind of subtyping you want run deeper than just naming ambiguity; 与Daniel Fischer所说的一样,为了扩展为什么这是不可能的:你想要的那种子类型的问题比命名歧义更深入; they make type inference a lot more difficult in general. 一般来说,他们使类型推理变得更加困难。 I think Scala's type inference is a lot more restricted and local than Haskell's for this reason. 由于这个原因,我认为Scala的类型推断比Haskell更受限制和局部化。

However, you can model this kind of thing with the type-class system: 但是,您可以使用类型系统对此类事物进行建模:

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

You can then use guards to "pattern-match" on these values: 然后,您可以使用警卫对这些值进行“模式匹配”:

displayAxis :: (HasAxes2D t) => t -> String
displayAxis axis
  | axis == axisX = "X"
  | axis == axisY = "Y"
  | otherwise = "Unknown"

This has many of the same drawbacks as subtyping would have: uses of axisX , axisY and axisZ will have a tendency to become ambiguous, requiring type annotations that defeat the point of the exercise. 这与子类型具有许多相同的缺点:使用axisXaxisYaxisZ会有变得模糊的倾向,需要类型注释来axisZ练习点。 It's also a fair bit uglier to write type signatures with these type-class constraints, compared to using concrete types. 与使用具体类型相比,使用这些类型类约束编写类型签名也是一件相当丑陋的事。

There's another downside: with the concrete types, when you write a function taking an Axes2D , once you handle X and Y you know that you've covered all possible values. 还有另一个缺点:对于具体类型,当你编写一个使用Axes2D的函数时,一旦你处理XY ,你就知道你已经涵盖了所有可能的值。 With the type-class solution, there's nothing stopping you from passing Z to a function expecting an instance of HasAxes2D . 使用类型类解决方案,没有什么可以阻止您将Z传递给期望HasAxes2D实例的HasAxes2D What you really want is for the relation to go the other way around, so that you could pass X and Y to functions expecting a 3D axis, but couldn't pass Z to functions expecting a 2D axis. 你真正想要的是关系反过来,这样你就可以将XY传递给期望3D轴的函数,但是不能将Z传递给期望2D轴的函数。 I don't think there's any way to model that correctly with Haskell's type-class system. 我认为没有办法用Haskell的类型系统正确建模。

This technique is occasionally useful — for instance, binding an OOP library like a GUI toolkit to Haskell — but generally, it's more natural to use concrete types and explicitly favour what in OOP terms would be called composition over inheritance , ie explicitly wrapping "subtypes" in a constructor. 这种技术偶尔会很有用 - 例如,将一个像GUI工具包这样的OOP库绑定到Haskell - 但一般来说,使用具体类型并明确支持OOP术语中的组合优于继承 ,即明确包装“子类型”更自然在构造函数中。 It's not generally much of a bother to handle the constructor wrapping/unwrapping, and it's more flexible besides. 处理构造函数包装/展开通常不是很麻烦,而且它更灵活。

It is not possible. 这不可能。 Note that in 请注意

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

the Axes2D in the Axes3D type is a value constructor taking no arguments, so Axes3D has two constructors, Axes2D and Z . Axes2DAxes3D类型是值构造不带参数,所以Axes3D有两个构造, Axes2DZ

Different types cannot have value constructors with the same name (in the same scope) because that would make type inference impossible. 不同的类型不能具有相同名称的值构造函数(在相同的范围内),因为这会使类型推断变得不可能。 What would 什么会

foo X = True
foo _ = False

have as a type? 作为一种类型? (It's a bit different with parametric types, all Maybe a have value constructors with the same name, and that works. But that's because Maybe takes a type parameter, and the names are shared only among types constructed with the same (unary) type constructor. It doesn't work for nullary type constructors.) (它与参数类型有点不同,所有Maybe a具有相同名称的值构造函数,并且可行。但这是因为Maybe接受一个类型参数,并且名称仅在使用相同(一元)类型构造函数构造的类型之间共享它对于nullary类型的构造函数不起作用。)

You can do it with Generalized Algebraic Data Types. 您可以使用广义代数数据类型。 We can create a generic (GADT) type with data constructors that have type constraints. 我们可以使用具有类型约束的数据构造函数创建泛型(GADT)类型。 Then we can define specialized types (type aliases) that specifies the full type and thus limiting which constructors are allowed. 然后我们可以定义指定完整类型的特殊类型(类型别名),从而限制允许哪些构造函数。

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

Now, you are guaranteed to only have X and Y passed into a function that is defined to take an argument of Axis2D . 现在,您可以保证只将XY传递给定义为采用Axis2D参数的Axis2D The constructor Z fails to match the type of Axis2D . 构造函数Z无法匹配Axis2D的类型。

Unfortunately, GADTs do not support automatic deriving , so you will need to provide your own instances, such as: 不幸的是,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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM