Я пишу движок Magic The Gathering (MTG) в Haskell.
Для тех, кто не знаком с MTG, это карточная игра, в которой карты могут иметь до 5 цветов: белый (W), синий (U), черный (B), красный (R) и зеленый (G).
{-# LANGUAGE ViewPatterns #-}
import Data.Set
data Color = W | U | B | R | G
deriving (Show, Eq, Ord)
data Card = Card (Set Color) -- simplified Card type with only its colors
viewColors :: Card -> [Color]
viewColors (Card colors) = toList colors
То, что я хотел бы сделать, это совпадение цветов по таким цветам:
foo :: Card -> String
foo (viewColors -> [W, B]) = "card is white and black"
foo _ = "whatever"
До сих пор так хорошо. Но здесь есть одна проблема: я могу неправильно набрать порядок цветов в шаблоне вида:
bar :: Card -> String
bar (viewColors -> [B, W]) = "this will never get hit"
bar _ = "whatever"
Конечно, я мог бы написать viewColors
таким образом, чтобы решить эту проблему напрямую. Или я мог бы использовать охранников, но я бы предпочел. Вот несколько способов сделать это
viewColors :: Card -> (Bool, Bool, Bool, Bool, Bool)
viewColors (Card colors) = let m = (`member` colors)
in (m W, m U, m B, m R, m G)
Это решение слишком многословно, хотя сопоставление шаблонов, даже если я использую тип, изоморфный Bool
, но с более короткими (и/или значимыми) идентификаторами. Согласование зеленой карты будет выглядеть как
baz :: Card -> String
baz (viewColors -> (False, False, False, False, True)) = "it green"
data ColorView = W | WU | WUB | ... all combos here
viewColors :: Card -> ColorView
viewColors (Card colors) = extract correct Colorview from colors
Это решение имеет комбинаторный взрыв. Слишком сложно реализовать, но приятно использовать, особенно если у меня есть colorViewToList :: ColorView -> [Color]
, чтобы позволить программное извлечение после соответствия шаблону.
Я понятия не имею, можно ли приблизить следующее в Haskell, но следующее было бы идеальным:
fuz :: Card -> String
fuz (viewColors -> (W :* ())) = "it white"
fuz (viewColors -> (W :* U :* ())) = "it white and blue"
fuz (viewColors -> (W :* B :* ())) = "it white and black"
Я хочу использовать расширенные расширения языка, чтобы разрешить этот тип кода: DataKinds, PolyKinds, TypeFamilies, MultiParamTypeClasses, GADT, вы называете его.
Что-то вроде этого возможно? Есть ли у вас другие предложенные подходы?