多种数据类型的对象

时间:2012-10-20 16:18:40

标签: object haskell types constructor

我需要为学校作业实施国际象棋游戏,你必须在同一块棋盘上制作适用于其他游戏的界面。所以,你必须实现国际象棋棋子,但也需要其他游戏。

我试着这样做:

data ChessPiece = King | Queen | Knight | Rook | Bishop | Pawn deriving (Enum, Eq, Show)
data Piece = ChessPiece | OtherGamePiece deriving (Enum, Eq, Show)
data ColoredPiece = White Piece | Black Piece
data Board = Board { boardData :: (Array Pos (Maybe ColoredPiece)) }

然后我尝试用:

加载国际象棋比赛的开始
beginBoard = Board (listArray (Pos 0 0, Pos 7 7) (pieces White ++ pawns White ++ space ++ pawns Black ++ pieces Black)) where
    pieces :: (Piece -> ColoredPiece) -> [Maybe ColoredPiece]
    pieces f = [Just (f Rook), Just (f Knight), Just (f Bishop), Just (f Queen), Just (f King), Just (f Bishop), Just (f Knight), Just (f Rook)]
    pawns :: (Piece -> ColoredPiece) -> [Maybe ColoredPiece]
    pawns f = (take 8 (repeat (Just (f Pawn))))
    space = take 32 (repeat Nothing)

我收到错误“无法匹配预期类型Piece' with actual type ChessPiece”     在f', namely Rook'的第一个论点中     在Just', namely(f Rook)的第一个论点中     在表达式中:Just(f Rook)“

所以,我觉得ChessPiece需要以某种方式'铸造'到(常规)作品。 (我知道,我正在使用命令式编程中的术语,但我希望我能在这里说清楚,如果需要,我会很乐意让我的问题更清楚。)

我正在尝试使构造成为可能吗? (有点像来自OO语言的类结构,但随后应用于数据类型,其中一种数据类型是另一种数据类型的子数据类型,并且一个对象可以同时是两种数据类型。例如,Rook是ChessPiece,因此一块) 我究竟做错了什么?关于如何实施我需要的结构的任何建议?

2 个答案:

答案 0 :(得分:10)

您所追求的通常被称为子类型。大多数OO语言使用子类实现子类型。

然而,Haskell绝对不是OO语言;事实上,它根本没有任何类型的子类型。令人高兴的是,使用“参数多态”通常可以获得相同的效果。现在,“参数多态”是一个可怕的术语!这是什么意思?

实际上,它有一个非常简单的含义:您可以编写适用于所有(具体)类型的代码。您已经知道如何使用的Maybe类型就是一个很好的例子。类型定义如下:

data Maybe a = Just a | Nothing

请注意它是如何写成Maybe a而不仅仅是Maybe; a是一个类型变量。这意味着,当您使用Maybe时,可以将其与任何类型一起使用。您可以拥有Maybe IntMaybe BoolMaybe [Int]甚至Maybe (Maybe (Maybe (Maybe Double)))

您可以使用此方法来定义您的电路板。对于基本的板功能,你不关心板上实际上是什么“片段” - 有些动作对任何块都有意义。另一方面,如果你关心这件作品的类型,你会关心的类型是什么,因为每个游戏的规则都是与众不同。

这意味着您可以使用某些类型变量来定义您的电路板。现在,您的董事会代表如下:

data Board = Board {boardData :: Array Pos (Maybe ColoredPiece)}

由于您希望将电路板概括为任何类型的片段,您需要添加一个类型变量,而不是指定ColoredPiece

data Board p = Board {boardData :: Array Pos p}

现在你已经为你可能想象的任何一种类型定义了Board类型!

因此,要将此棋盘表示用于棋子,您需要将棋子类型传递给新的Board类型。这看起来像这样:

type ChessBoard = Board ColoredPiece

(作为参考,type只创建一个同义词 - 现在写ChessBoard完全等同于编写Board ColoredPiece。)

现在,只要您有棋盘,请使用新的ChessBoard类型。

此外,您可以编写一些适用于任何板的有用功能。例如,让我们想象你要做的就是得到一些碎片清单。那么这个函数的类型是:

listPieces :: Board p -> [p]

您可以在函数类型中使用类型变量(如p)编写一大堆其他类似函数,这些函数不关心实际的部分。此功能现在适用于您提供的任何电路板,包括Board ColoredPiece,否则称为ChessBoard

总结:您希望以多态方式编写Board表示。这使您可以获得与尝试使用子类型相同的效果。

答案 1 :(得分:2)

Tikhon的解决方案是要走的路。但是,仅供参考,请注意类型构造函数与数据构造函数之间的区别。就在这里,例如:

data ChessPiece = King | Queen | Knight | Rook | Bishop | Pawn deriving (Enum, Eq, Show)
data Piece = ChessPiece | OtherGamePiece deriving (Enum, Eq, Show)

这不起作用,因为你在第一行中定义了一个名为ChessPiece的类型构造函数,在另一行中定义了一个名为ChessPiece的数据构造函数,这些都不是一回事。类型构造函数表示类似:“ChessPiece类型可以是King,或Queen,或......”,而数据构造函数只是创建通用数据(也会发生)被称为ChessPiece)。

您可以做的是重新定义Piece类型的第一个数据构造函数;一些名为ChessPiece的通用数据,其中包含有关类型 ChessPiece的一些信息。以下typechecks:

data ChessPiece   = King | Queen | Knight | Rook | Bishop | Pawn deriving (Enum, Eq, Show)
data Piece        = ChessPiece ChessPiece | OtherGamePiece  -- note the change 
data ColoredPiece = White Piece | Black Piece

你可以改变你的功能:

pieces :: (Piece -> ColoredPiece) -> [Maybe ColoredPiece]
pieces f = [Just (f (ChessPiece Rook)), Just (f (ChessPiece Knight)), Just (f (ChessPiece Bishop)), Just (f (ChessPiece Queen)), Just (f (ChessPiece King)), Just (f (ChessPiece Bishop)), Just (f (ChessPiece Knight)), Just (f (ChessPiece Rook))]

为了使类型和数据构造函数之间的区别更加明显,这里是一个限制版本,它为每个构造使用不同的名称:

data ChessRoyalty = King | Queen
data Piece        = ChessPiece ChessRoyalty | OtherGamePiece
data ColoredPiece = White Piece | Black Piece