I need to implement a chess game for a school assignment, and you have to make an interface that will work for other games on the same board. So, you have to implement chess pieces, but also pieces for other games.
I tried to do this:
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)) }
Then I try to load the begin f the chess game with:
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)
And I get the error “Couldn’t match expected type Piece' with actual typeChessPiece’
In the first argument of f', namelyRook’
In the first argument of Just', namely(f Rook)’
In the expression: Just (f Rook)”
So, I’ve the feeling that the ChessPiece needs to be ‘casted’ to a (regular) Piece somehow.
(I know, I am using terms from imperative programming, but I hope that I make myself clear here, I will be happy to make my question clearer if needed).
Is the construct that I’m trying to make possible? (sort of like a class structure from OO languages, but then applied to datatypes, where one datatype is a sub-datatype from the other, and an object can be two datatypes at the same time. For example a Rook is a ChessPiece and therefore a Piece)
What am I doing wrong? Any suggestions on how to implement the structure I need?
What you are after is normally referred to as sub-typing. Most OO languages achieve sub-typing using sub-classes.
Haskell, however, is decidedly not an OO language; in fact, it does not really have any sort of sub-typing at all. Happily, you can usually achieve much the same effect using “parametric polymorphism”. Now, “parametric polymorphism” is a scary-sounding term! What does it mean?
In fact, it has a very simple meaning: you can write code that works for all (concrete) types. The
Maybetype, which you already know how to use, is a great example here. The type is defined as follows:note how it is written as
Maybe arather than justMaybe; theais a type variable. This means that, when you go to useMaybe, you can use it with any type. You can have aMaybe Int, aMaybe Bool, aMaybe [Int]and even aMaybe (Maybe (Maybe (Maybe Double))).You can use this approach to define your board. For basic board functions, you do not care about what “piece” is actually on the board–there are some actions that make sense for any piece. On the other hand, if you do care about the type of the piece, you will be caring about what the type is exactly, because the rules for each game are going to be different.
This means that you can define your board with some type variable for pieces. Right now, your board representation looks like this:
since you want to generalize the board to any sort of piece, you need to add a type variable instead of specifying
ColoredPiece:now you’ve defined a
Boardtype for any piece type you could possibly imagine!So, to use this board representation for chess pieces, you need to pass the type of the piece to your new
Boardtype. This will look something like this:(For reference,
typejust creates a synonym–now writingChessBoardis completely equivalent to writingBoard ColoredPiece.)So now, whenever you have a chess board, use your new
ChessBoardtype.Additionally, you can write some useful functions that work on any board. For example, let’s imagine all you want to do is get a list of the pieces. The type of this function would then be:
You can write a whole bunch of other similar functions that don’t care about the actual piece by using type variables like
pin your function types. This function will now work for any board you give it, including aBoard ColoredPiece, otherwise know asChessBoard.In summary: you want to write your
Boardrepresentation polymorphically. This lets you achieve the same effect as you wanted to try with sub-typing.