Скрытие объявлений экземпляров класса типов при импорте в Haskell
Я пытаюсь сделать игру в крестики-нолики, и я решил создать типы для ячеек (элементов доски) и доски следующим образом:
data Cell = X | O deriving (Show, Eq)
type Board = [[Maybe Cell]]
Здесь Nothing представляет пустую ячейку, (Just X) и (Just O) представляют ячейки, заполненные X и O соответственно.
Я хотел бы определить (может быть, ячейка) как моноид следующим образом:
instance Monoid (Maybe Cell) where
mempty = Nothing
mappend Nothing x = x
mappend (Just x) _ = (Just x)
И доска как еще один моноид с
instance Monoid Board where
mempty = [[Nothing, Nothing, Nothing]
,[Nothing, Nothing, Nothing]
,[Nothing, Nothing, Nothing]]
mappend = zipWith (zipWith mappend)
-- where the right-hand-side mappend is the addition on (Maybe Cell)
Я знаю, что мог бы реализовать это абсолютно без моноидов, но я пытаюсь исследовать поле, и это просто очень хороший способ написать это.
Проблема в том, что я Maybe
экземпляр моноида уже определен в GHC.Base
следующее:
instance Semigroup a => Monoid (Maybe a)
Это определение очень отличается от того, что я хочу, но оно вызывает дублирование объявлений экземпляров, поэтому я не могу просто проигнорировать это.
Я пытаюсь скрыть Monoid
экземпляр (Maybe a)
от GHC.Base
чтобы избежать дублирования экземпляров. Я много пытался найти это, но не смог найти способ это скрыть. Я не могу скрыть все Monoid
или все Semigroup
потому что мне нужны их функции, но мне нужно скрыть это объявление конкретного экземпляра. Может ли кто-нибудь помочь мне с этим?
ПРИМЕЧАНИЕ: я использую FlexibleInstances.
1 ответ
В стандартном Haskell экземпляры классов всегда "полностью глобальны"† - если у типа есть экземпляр для данного класса где-то, то этот экземпляр используется везде.
Итак, если вы хотите определить отдельный экземпляр, вам нужно либо иметь другой класс - обычно непрактичный, в том числе в вашем примере - либо другой тип, который обычно не является проблемой. На самом деле у Haskell есть специальное ключевое слово для такого рода вещей, newtype
, Вы просто меняете type Board = [[Maybe Cell]]
в
newtype Board = Board [[Maybe Cell]]
а потом
instance Semigroup Board where
Board l <> Board r = Board $ zipWith (zipWith mappend) l r
instance Monoid Board where
mempty = Board [[Nothing, Nothing, Nothing]
,[Nothing, Nothing, Nothing]
,[Nothing, Nothing, Nothing]]
mappend = (<>)
Аналогично вместо Maybe Cell
Вы должны использовать другой тип, который имеет подходящий Monoid
пример. Это на самом деле уже существует в базовой библиотеке, но это на самом деле не нужно: вы можете просто сделать экземпляр полугруппы (не моноид!) Для Cell
сам, который представляет собой левое смещение, то Maybe
будет (начиная с GHC-8.4) автоматически иметь желаемое поведение.
instance Semigroup Cell where
a <> _ = a
†Фактически было предложено ослабить это, допуская локально выбранные случаи, в документе, представленном на Симпозиуме по Хаскеллу в 2018 году.