Скрытие объявлений экземпляров класса типов при импорте в 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 году.

Другие вопросы по тегам