我需要为学校作业实现一个国际象棋游戏,你必须制作一个界面,该界面适用于同一棋盘上的其他游戏。因此,您必须实现棋子,还必须实现其他游戏的棋子。
我试图这样做:
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
的第一个参数中车'在
Just', namely
的第一个参数中(f Rook)'在表达中:Just (f Rook)"
所以,我觉得 ChessPiece 需要以某种方式“类型转换”到(常规)Piece。
(我知道,我使用的是命令式编程中的术语,但我希望我在这里说清楚,如果需要,我很乐意让我的问题更清楚)。
我试图实现的构造是否成为可能? (有点像 OO 语言中的类结构,但随后应用于数据类型,其中一种数据类型是另一种数据类型的子数据类型,并且一个对象可以同时是两种数据类型。例如,Rook 是 ChessPiece,因此一块)
我究竟做错了什么?关于如何实现我需要的结构的任何建议?
最佳答案
您所追求的通常称为子类型。大多数面向对象语言使用子类实现子类型化。
然而,Haskell 绝对不是面向对象的语言。事实上,它根本没有任何类型的子类型。令人高兴的是,您通常可以使用“参数多态性”获得相同的效果。现在,“参数多态性”是一个听起来很可怕的术语!这是什么意思?
事实上,它有一个非常简单的含义:您可以编写适用于所有(具体)类型的代码。 Maybe
您已经知道如何使用的 type 就是一个很好的例子。类型定义如下:
data Maybe a = Just a | Nothing
注意它是如何写成
Maybe a
而不仅仅是 Maybe
; a
是一个类型变量。这意味着,当你去使用 Maybe
,您可以将其用于任何类型。您可以拥有一个 Maybe Int
, Maybe Bool
, Maybe [Int]
甚至还有一个 Maybe (Maybe (Maybe (Maybe Double)))
.您可以使用这种方法来定义您的电路板。对于基本的棋盘功能,您并不关心棋盘上实际的“棋子”是什么——有些 Action 对任何棋子都有意义。另一方面,如果你真的关心棋子的类型,你就会关心到底是什么类型,因为每个游戏的规则都会不同。
这意味着您可以为棋子定义一些类型变量的棋盘。现在,您的董事会代表如下所示:
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
多态表示。这可以让您获得与您想要尝试使用子类型相同的效果。
关于多种数据类型的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12990329/