展开Haskell数据类型

Xoc*_*lli 2 haskell types

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

例如:以下编译:

data Axes2D = X | Y
data Axes3D = Axes2D | Z
Run Code Online (Sandbox Code Playgroud)

但是,以下内容:

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) 
Run Code Online (Sandbox Code Playgroud)

给出以下编译错误(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.
Run Code Online (Sandbox Code Playgroud)

那么,有可能使XY类型Axes2D以及类型Axes3D?如果有可能:我做错了什么?另外:为什么不可能?

ehi*_*ird 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
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用警卫对这些值进行"模式匹配":

displayAxis :: (HasAxes2D t) => t -> String
displayAxis axis
  | axis == axisX = "X"
  | axis == axisY = "Y"
  | otherwise = "Unknown"
Run Code Online (Sandbox Code Playgroud)

这与子类型具有许多相同的缺点:使用axisX,axisY并且axisZ将具有变得模糊的倾向,需要类型注释来破坏练习的点.与使用具体类型相比,使用这些类型类约束编写类型签名也是一件相当丑陋的事.

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

这种技术偶尔会很有用 - 例如,将一个像GUI工具包这样的OOP库绑定到Haskell - 但一般来说,使用具体类型并明确支持OOP术语中的组合优于继承,即明确包装"子类型" 更自然在构造函数中.处理构造函数包装/展开通常不是很麻烦,而且它更灵活.


Dan*_*her 8

这不可能.请注意

data Axes2D = X | Y
data Axes3D = Axes2D | Z
Run Code Online (Sandbox Code Playgroud)

Axes2DAxes3D类型是值构造不带参数,所以Axes3D有两个构造,Axes2DZ.

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

foo X = True
foo _ = False
Run Code Online (Sandbox Code Playgroud)

作为一种类型?(它与参数类型有点不同,它们都Maybe a具有相同名称的值构造函数,并且可以工作.但这是因为Maybe它接受一个类型参数,并且名称仅在使用相同(一元)类型构造函数构造的类型之间共享.适用于nullary类型的构造函数.)

  • @Xochipilli:不; 无法判断任何类型的未知值是否为"X"; 这会破坏[参数化](http://stackoverflow.com/questions/8524801/determining-function-behavior-from-the-type-of-the-function).它必须类似于`foo ::([has-constructor] X)=> a - > Bool`. (2认同)