5

我需要为学校作业实现一个国际象棋游戏,并且您必须制作一个适用于同一棋盘上其他游戏的界面。因此,您必须实现棋子,但也必须实现其他游戏的棋子。

我试图这样做:

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 typeChessPiece' 在f', namelyRook 的第一个参数中' 在Just', namely(f Rook) 的第一个参数中' 在表达式中:Just (f Rook)”

所以,我觉得 ChessPiece 需要以某种方式“铸造”为(常规)棋子。(我知道,我使用的是命令式编程中的术语,但我希望我在这里清楚自己,如果需要,我很乐意让我的问题更清楚)。

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

4

2 回答 2

10

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

然而,Haskell 绝对不是一种面向对象的语言。事实上,它根本没有任何类型的子类型。令人高兴的是,您通常可以使用“参数多态性”实现大致相同的效果。现在,“参数多态性”是一个听起来很吓人的术语!这是什么意思?

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

data Maybe a = Just a | Nothing

注意它是如何写的,Maybe a而不仅仅是Maybe; 这a是一个类型变量。这意味着,当您使用 时Maybe,您可以将其与任何类型一起使用。你可以有一个Maybe Int,一个Maybe Bool,一个Maybe [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只需创建一个同义词——现在 writingChessBoard完全等同于 writing Board ColoredPiece。)

所以现在,只要你有棋盘,就用你的新ChessBoard类型。

此外,您可以编写一些适用于任何电路板的有用函数。例如,让我们假设您想要做的只是获取一个片段列表。该函数的类型将是:

listPieces :: Board p -> [p]

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

总之:你想Board多态地编写你的表示。这使您可以实现与想要尝试使用子类型相同的效果。

于 2012-10-20T17:02:26.290 回答
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类型可以是 a King,或 a Queen,或 a...”,而数据构造函数只是创建通用数据(也恰好被称为ChessPiece)。

您可以做的是重新定义该Piece类型的第一个数据构造函数;一些名为的通用数据包含有关引擎盖下的类型ChessPiece的一些信息。以下类型检查: ChessPiece

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
于 2012-10-20T17:13:29.437 回答