我正在尝试制作一个井字游戏,我决定构建单元格(板的元素)和板的类型如下:
data Cell = X | O deriving (Show, Eq)
type Board = [[Maybe Cell]]
这里,Nothing表示空单元格(Just X)和(Just O)分别表示用X和O填充的单元格。
我想将(Maybe Cell)定义为monoid,如下所示:
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
monoid实例已经在GHC.Base
中定义如下:
instance Semigroup a => Monoid (Maybe a)
这与我想要的定义截然不同,但它会导致重复的实例声明,所以我不能忽略它。
我想要做的是隐藏Monoid
的(Maybe a)
GHC.Base
实例以避免重复实例。我尝试了很多搜索,但实际上找不到隐藏它的方法。我无法隐藏所有Monoid
或所有Semigroup
,因为我需要它们的功能,但我需要隐藏这个特定的实例声明。有人可以帮我吗?
注意:我正在使用FlexibleInstances。
我标准的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
。那个实际上是exists already in the base library,但它并不是真的有必要:你可以为Cell
本身制作一个半群(不是monoid!)实例,它代表左偏,然后Maybe
将(因为GHC-8.4)自动拥有所需的行为。
instance Semigroup Cell where
a <> _ = a
†实际上已经提议放宽这个,允许在a paper presented at the 2018 Haskell Symposium中本地选择的实例。