展开Haskell数据类型
是否可以用新值扩展数据类型 例如: 汇编如下:展开Haskell数据类型,haskell,types,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 :: Point
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)
给出以下编译错误(沿\u轴移动\u 3D
注释掉不会给出错误):
因此,是否可以将Axes2D
类型的X
和Y
以及Axes3D
类型的?
如果可能的话:我做错了什么?否则:为什么不可能?不可能。注意,在
data Axes2D = X | Y
data Axes3D = Axes2D | Z
Axes3D
类型中的Axes2D
是一个不带参数的值构造函数,因此Axes3D
有两个构造函数,Axes2D
和Z
不同类型的值构造函数不能具有相同的名称(在相同的范围内),因为这将使类型推断不可能。你会怎么做
foo X = True
foo _ = False
作为一种类型你有吗?(与参数化类型有点不同,所有可能a
都有同名的值构造函数,这是可行的。但这是因为可能
接受一个类型参数,并且名称仅在使用相同(一元)类型构造函数构造的类型之间共享。它不适用于空类型构造函数。)与丹尼尔·菲舍尔(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
传递给需要三维轴的函数,但不能将Z
传递给需要二维轴的函数。我认为没有任何方法可以用Haskell的类型类系统正确地建模
这种技术有时很有用——例如,将一个OOP库(如GUI工具包)绑定到Haskell——但一般来说,更自然的做法是使用具体的类型,并明确支持OOP术语中的调用,即在构造函数中显式包装“子类型”。处理构造函数包装/展开通常不太麻烦,而且更灵活。可以使用广义代数数据类型。我们可以使用具有类型约束的数据构造函数创建泛型(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不支持自动派生
,因此您需要提供自己的实例,例如:
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
啊,Axes3D
中的Axes2D
是一个值构造函数,而另一个Axes2D
是一个类型构造函数。所以这些是不同的,我说的对吗?foo会有typea->Bool
?首先正确,类型构造函数和值构造函数甚至在不同的命名空间中。由于没有同时出现类型构造函数和值构造函数的地方,因此可以根据名称所表示的语法来确定,因此值构造函数与类型构造函数具有相同的名称没有问题。第二个错误,因为只有极少数类型具有值构造函数X
。因此,如果可能的话,foo的类型是(由语法组成)foo::(HasConstructor X a)=>a->foo
@Xochipilli:No;无法判断任何类型的未知值是否为X
;那会打破的。它必须类似于foo::(a[has constructor]X)=>a->Bool
(事实上,(a[has constructor]X)
基本上对应于我答案的(hasaxes2da)
,除了我将X
和Y
捆绑到同一个类型类中之外,如果将Axis2D
和Axis3D
放在单独的模块中,两种数据类型对于某些构造函数可能具有相同的名称。但是,如果您希望同时使用这两个模块,则必须导入它们,以解决歧义。
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