首页 > 解决方案 > 在 Haskell 中导入时隐藏类型类实例声明

问题描述

我正在尝试制作井字游戏,我决定为单元格(棋盘的元素)和棋盘构建类型,如下所示:

data Cell  = X | O deriving (Show, Eq)
type Board = [[Maybe Cell]]

这里,Nothing 表示一个空单元格,(Just X) 和 (Just O) 分别表示填充有 X 和 O 的单元格。

我想将 (Maybe Cell) 定义为一个幺半群,如下所示:

instance Monoid (Maybe Cell) where
  mempty             = Nothing
  mappend Nothing x  = x
  mappend (Just x) _ = (Just x)

和 Board 作为另一个幺半群

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)

我知道我完全可以在没有幺半群的情况下实现这个,但我正在尝试探索这个领域,它只是一种非常简洁的编写方式。

我得到的问题是一个Maybemonoid 实例已经定义GHC.Base如下:

instance Semigroup a => Monoid (Maybe a)

这与我想要的定义非常不同,但它会导致重复的实例声明,所以我不能忽略它。

我想要做的是隐藏from的Monoid实例以避免重复实例。我尝试了很多搜索,但无法真正找到隐藏它的方法。我不能隐藏全部或全部,因为我需要它们的功能,但我需要隐藏这个特定的实例声明。有人可以帮我吗?(Maybe a)GHC.BaseMonoidSemigroup

注意:我使用的是 FlexibleInstances。

标签: haskelltypeclassmonoidssemigroup

解决方案


在标准的 Haskell 中,类实例总是“完全全局的”<sup>†</sup>——如果一个类型在某处有一个给定类的实例,那么这个实例就会在任何地方使用。

所以,如果你想定义一个单独的实例,你需要有一个不同的类——通常不实用,包括在你的例子中——或者一个不同的类型,这通常不是问题。事实上,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

†</sup>在 2018 年 Haskell 研讨会上发表的一篇论文中,实际上已经提议放宽这一点,允许本地选择实例。


推荐阅读