I am trying to make a tic-tac-toe game, and I decided to construct types for cells (elements of the board) and the board as follows:
data Cell = X | O deriving (Show, Eq)
type Board = [[Maybe Cell]]
Here, Nothing represents an empty cell, (Just X) and (Just O) represent cells filled with X and O respectively.
I would like to define (Maybe Cell) as a monoid as follows:
instance Monoid (Maybe Cell) where
mempty = Nothing
mappend Nothing x = x
mappend (Just x) _ = (Just x)
And Board as another monoid with
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)
I know I could implement this absolutely without monoids, but I'm trying to explore the field, and it's just a really neat way to write it.
The problem that I get is that a Maybe
monoid instance is already defined in GHC.Base
as follows:
instance Semigroup a => Monoid (Maybe a)
This has a very different definition than what I want, but it causes duplicate instance declarations, so I can't just ignore it.
What I'm trying to do is to hide the Monoid
instance of (Maybe a)
from GHC.Base
to avoid duplicate instances. I tried searching a lot for that, but couldn't really find a way to hide that. I can't hide all of Monoid
or all of Semigroup
, because I need their functions, but I need to hide this specific instance declaration. Could anyone help me with that?
NOTE: I'm using FlexibleInstances.
I standard Haskell, class instances are always “completely global”† – if a type has an instance for a given class somewhere, then this instance is used everywhere.
So, if you want to define a separate instance, you need to either have a different class – usually not practical, including in your example – or a different type, which is usually not a problem. In fact Haskell has a dedicated keyword for this kind of thing, newtype
. You simply change type Board = [[Maybe Cell]]
to
newtype Board = Board [[Maybe Cell]]
and then
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 = (<>)
Likewise, instead of Maybe Cell
you should use another type that has the suitable Monoid
instance. That one actually exists already in the base library, but it's not really necessary: you can just make a semigroup (not monoid!) instance for Cell
itself which represents the left-bias, then Maybe
will (since GHC-8.4) automatically have the desired behaviour.
instance Semigroup Cell where
a <> _ = a
†It has actually been proposed to relax this, allowing locally-selected instances, in a paper presented at the 2018 Haskell Symposium.