Haskell 数据字段中的多态性

Polymorphism in Haskell data fields

我正在尝试通过创建一个简单的国际象棋游戏来学习 Haskell。但是,我在定义表示棋盘正方形的函数时遇到了问题

import Data.Char
type Board = [[Square]]
type Square = Maybe Piece
data Piece = Piece PieceColor PieceType deriving (Show, Eq)
data PieceColor = White | Black deriving (Show, Eq)
data PieceType = King | Queen | Rook | Bishop | Knight | Pawn deriving (Show, Eq)

...    

displaySquare :: Square -> Char
    displaySquare n
        | n == Nothing = ' '
        | n == Just (Piece White _) = displaySquare' n
        | otherwise = toLower (displaySquare' n)
            where
                displaySquare'   (Just (Piece _ King))   = 'K'
                displaySquare'   (Just (Piece _ Queen))  = 'Q'
                displaySquare'   (Just (Piece _ Rook))   = 'R'
                displaySquare'   (Just (Piece _ Bishop)) = 'B'
                displaySquare'   (Just (Piece _ Knight)) = 'N'
                displaySquare'   (Just (Piece _ Pawn))   = 'P'

尝试 运行 GHCI returns 出现以下错误:

Chess.hs:21:30:
    Found hole ‘_’ with type: PieceType
    Relevant bindings include
      displaySquare' :: Maybe Piece -> Char (bound at Chess.hs:24:13)
      n :: Square (bound at Chess.hs:19:15)
      displaySquare :: Square -> Char (bound at Chess.hs:19:1)
    In the second argument of ‘Piece’, namely ‘_’
    In the first argument of ‘Just’, namely ‘(Piece White _)’
    In the second argument of ‘(==)’, namely ‘Just (Piece White _)’
Failed, modules loaded: none.

我不太确定错误试图告诉我什么。如果非要我猜的话,我会说问题是将数据字段定义为空洞“_”,因为这意味着该函数可以将任何类型作为数据字段,这显然与给定的类型签名冲突。我的假设是否正确,我该如何解决?

这是displaySquare模式匹配的写法:

displaySquare Nothing = ' '
displaySquare n@(Just (Piece White _)) = displaySquare' n
displaySquare n                        = toLower (displaySquare' n)
  where
    ...your definition of displaySquare'...

您在 displaySquare' 的定义中正确使用了模式匹配。

注意语法 n@(Just (Piece White _)) 是如何工作的——它提供了一个模式来匹配:Just White _ 并且它还将变量 n 设置为传递给函数的参数。

更新

正如@behzad.nouri 在评论中提到的,您需要将辅助函数放在顶层。

将辅助函数保持在本地的另一种编写方式:

displaySquare n =
  case n of
    Nothing              -> ' '
    Just (Piece White _) -> displaySquare' n
    _                    -> toLower (displaySquare' n)
  where
    displaySquare' x = ...

在这种情况下,n 绑定到 displaySquare 的第一个参数,因此您不需要使用 n@... 语法。

关键在于:模式匹配的工作原理与相等比较根本不同(通常更可取)!

通过模式匹配,你只看一块数据是否“符合你感兴趣的一些粗略形状”。您可能会完全忽略您 感兴趣的部分数据,就像您尝试使用 Just (Piece White _) 一样。因此,模式匹配通常是一种“模糊操作”。

OTOH,== 只是一个运算符 (library-defined function),它接受两个值并确定它们是否 完全相等 Piece White _ 实际上不是一个值,您需要给出具体的 PieceType 以适应 _ 差距。这就是 GHC 试图通过消息 Found hole ‘_’ with type: PieceType 告诉您的内容:如果您确实需要建立一个 Square 值,则需要提供一个 PieceType 值。当你处理复杂的问题时,这些类型化的漏洞非常有用:从外部开始,例如你可以从 n == Piece _ _ 开始,GHC 会告诉你它期望在第一个空隙中有一个 PieceColor,你可以插入 White,GHC 会告诉你它期望在第二个空隙中有一个 PieceType, 等等..

只是,在 displaySquare 函数中,实际构造一个具体的 Square 值来进行比较并没有什么意义:你真正想做的是 解构 这样的值。这就是模式匹配更加方便的地方。事实上,我会把这个函数写成一个大的子句列表:

displaySquare (Just (Piece White King))   = '♔'
displaySquare (Just (Piece White Queen))  = '♕'
displaySquare (Just (Piece White Rook))   = '♖'
displaySquare (Just (Piece White Bishop)) = '♗'
displaySquare (Just (Piece White Knight)) = '♘'
displaySquare (Just (Piece White Pawn))   = '♙'
displaySquare (Just (Piece Black pt))
   = toEnum . (+6)  -- Unicode U+2659 WHITE CHESS PAWN
                    --  is followed by BLACK CHESS KING, etc.
      . fromEnum . displaySquare . Just $ Piece White pt
displaySquare Nothing = ' '