Полиморфизм в полях данных Haskell

Я пытаюсь изучить 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 возвращает следующую ошибку:

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.

Я не совсем уверен, что ошибка пытается сказать мне. Если бы мне пришлось угадывать, я бы сказал, что проблема заключается в определении поля данных как дыры '_', поскольку это означает, что функция может принимать любой тип в качестве поля данных, что явно противоречит заданной сигнатуре типа. Верно ли мое предположение и как мне его решить?


person Nicolás Siplis    schedule 14.11.2015    source источник
comment
Вы пытались использовать синтаксис шаблона (_) в выражении, что обычно приводило к ошибке. Однако _ теперь также является допустимым выражением, поэтому компиляция предположила, что вы знаете, что делаете, и намеренно поместила туда _. В синтаксисе выражений это называется типизированной дырой — в основном, это используется для программирования, ориентированного на тип, поскольку компилятор выдает всю информацию о типе вокруг этой привязки. Компилятор, вероятно, должен распознавать типизированные дыры только в том случае, если они включены... в противном случае он должен выдавать ошибку синтаксического анализа/синтаксиса, чтобы избежать подобной путаницы.   -  person user2407038    schedule 14.11.2015
comment
@user2407038 user2407038, такая путаница встречается редко, и ее избегание не стоит неудобств, связанных с тем, чтобы заставить всех включать расширение для использования типизированных отверстий.   -  person dfeuer    schedule 14.11.2015


Ответы (2)


Вот как можно написать 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@....

person ErikR    schedule 14.11.2015
comment
это не скомпилируется, так как вспомогательная функция не входит в область действия во 2-й строке - person behzad.nouri; 14.11.2015
comment
Это также выдает ошибку, поскольку n@(Just White _) должно быть n@(Just (Piece White _). Однако исправление этого и определение вспомогательной функции как обычной сработало! Из любопытства, есть ли способ определить вспомогательную функцию, чтобы она оставалась внутри displaySquare? Поиск в Google вспомогательных функций для нескольких функций, похоже, ничего не дал. - person Nicolás Siplis; 14.11.2015
comment
Ответ отлично работает! Использование case делает его более читабельным. - person Nicolás Siplis; 14.11.2015
comment
@NicolásSiplis Вы также могли бы сохранить другие охранники (и избежать перемещения вспомогательной функции) с помощью охранника шаблона: | Just (Piece White _) <- n (стандарт с Haskell 2010). - person Ørjan Johansen; 14.11.2015

Важный момент заключается в следующем: сопоставление с образцом работает принципиально иначе, чем сравнение на равенство (и обычно гораздо предпочтительнее)!

При сопоставлении с образцом вы смотрите только на то, «соответствует ли часть данных некоторой грубой форме, которая вас интересует». Вы можете полностью игнорировать те части данных, которые вам не интересны, как вы пытались с Just (Piece White _). Таким образом, сопоставление с образцом обычно является «нечеткой операцией».

OTOH, == — это просто оператор (функция, определяемая библиотекой), которая принимает два значения и определяет, являются ли они полностью равными. 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 = ' '
person leftaroundabout    schedule 23.03.2017