我有以下数据类型:
data Bull = Fools
| Twoo
deriving (Eq, Show)
并使用Monoid实现它:
instance Monoid Bull where
mempty = Fools
mappend _ _ = Fools
如您所见,mempty
是身份法不具备的身份功能:
*Main> x = Twoo
*Main> mappend mempty x == x
Bull
类型的身份是什么?
Bool
类型的身份是什么?
答案 0 :(得分:14)
简短回答:取决于mappend
功能。
Bull
类型的身份是什么?Bool
类型的身份是什么?
类型具有无“固有”标识,标识元素仅存在关于二元函数(此处为{{1} }),就像Wikipedia article says:
一样在数学中,一个单位元素或中性元素是一组特殊类型的元素,相对于该集合上的二元运算,当与它们结合时,其他元素保持不变。
所以这取决于操作mappend
是什么。
如果我们定义mappend
Bool
,则标识元素为mappend = (&&)
。但是,如果我们选择mempty = True
,那么mappend = (||)
。
您的mempty = False
不正确。由于它不能满足财产:
instance Moniod Bull
如果我们选择mappend mempty x = x
作为Fools
,则mempty = Fools
应为mappend Fools Twoo
。如果我们选择Twoo
,则mempty = Twoo
仍然不是mappend Twoo Twoo
。
Twoo
的要点是你必须仔细设计二元运算符。就像Haskell documentation on Monoid
所说的那样,它应该满足以下要求:
Monoid
这些规则并非为Haskell“发明”: monoid 是众所周知的algebraic structure。通常在数学中,幺半群表示为3元组。例如(N,+,0) N 此处设置(例如自然数), + 二进制函数,< em> 0 标识元素。
答案 1 :(得分:6)
这是一个很好的问题,也是我以前玩过几次的问题。事实上,这是我提出的universe的第一个用途之一,我仍然认为它是一个整洁的用途。那么,让我告诉你!
以下是我们的想法:我们将使用Universe包来枚举 mempty
和mappend
的所有可能实现,然后检查哪些符合法律。首先,一些样板:
import Data.Universe
import Data.Universe.Instances.Reverse
data Bull = Fools | Twoo deriving (Bounded, Enum, Eq, Ord, Read, Show)
instance Universe Bull
instance Finite Bull
这只是导入包的相应位并定义您的类型。现在,让我们编写幺半群法则。我们希望我们的mappend
是关联的;为(+)
撰写mappend
,我们可以要求:
associative (+) = all (\(x,y,z) -> (x+y)+z == x+(y+z)) universe
身份法律彼此非常相似,并将我们的mappend
与我们的mempty
联系起来(我们会在此处拨打(+)
和zero
):
leftIdentity zero (+) = all (\x -> zero+x == x) universe
rightIdentity zero (+) = all (\x -> x+zero == x) universe
幺半群应该满足所有三个法则:
monoid (zero, (+)) = associative (+) && leftIdentity zero (+) && rightIdentity zero (+)
现在我们可以通过过滤出符合法律的那些来构建所有幺半群的列表:
monoidsOnBull :: [(Bull, Bull -> Bull -> Bull)]
monoidsOnBull = filter monoid universe
让我们在ghci中查看:
> mapM_ print monoidsOnBull
(Twoo,[(Fools,[(Fools,Fools),(Twoo,Fools)]),(Twoo,[(Fools,Fools),(Twoo,Twoo)])])
(Fools,[(Fools,[(Fools,Fools),(Twoo,Twoo)]),(Twoo,[(Fools,Twoo),(Twoo,Fools)])])
(Twoo,[(Fools,[(Fools,Twoo),(Twoo,Fools)]),(Twoo,[(Fools,Fools),(Twoo,Twoo)])])
(Fools,[(Fools,[(Fools,Fools),(Twoo,Twoo)]),(Twoo,[(Fools,Twoo),(Twoo,Twoo)])])
(旁白:我们应该如何阅读此输出?嗯,Universe包显示类型a -> b
的函数,方法是显示类型[(a, b)]
的图形,即输入和输出对的列表。上面输出的每一行都是一个元组,在第一部分中有一个合适的mempty
,在第二部分中有一个合适的mappend
。)
那么这些幺半群做什么?让我们一次拿一个:
(Twoo,[(Fools,[(Fools,Fools),(Twoo,Fools)]),(Twoo,[(Fools,Fools),(Twoo,Twoo)])])
除非两个输入均为mappend
,否则Fools
输出Twoo
。也就是说,这是Bull
相当于(&&)
。在(&&)
的案例中,True
的身份为Twoo
- 或Bull
。
(Fools,[(Fools,[(Fools,Fools),(Twoo,Twoo)]),(Twoo,[(Fools,Twoo),(Twoo,Fools)])])
如果两个输入相等,则此mappend
输出Fools
,否则输出Twoo
。您可以将此视为Bool
上的xor,或者1位数字上的两个补码。它的身份是Fools
(或零)。
(Twoo,[(Fools,[(Fools,Twoo),(Twoo,Fools)]),(Twoo,[(Fools,Fools),(Twoo,Twoo)])])
这个就像最后一个,但在任何地方都被否定了。
(Fools,[(Fools,[(Fools,Fools),(Twoo,Twoo)]),(Twoo,[(Fools,Twoo),(Twoo,Twoo)])])
这个就像第一个,但到处都是否定的。它也恰好与(||)
Bool
上的False
一样,其身份为base
。
这结束了讲座,但还有另外两个有趣的补充说明。
首先,当mappend
分别为(&&)
和(||)
时,Monoid
会提供All
和Any
幺半群。据我所知,没有一个合适的新类型可以得到xor或它的否定为Num
;但是你可以通过为Bool
声明一个Word1
实例来伪造它(使用False
直觉True
为0而Sum Bool
为1)来获取它data Color = Red | Green | Blue
。
第二,这里的另一个答案是:> length monoidsOnColor
33
有什么幺半群?我们现在有了解决这个问题的所有机制,并确认实际上有很多幺半群:
{{1}}
我鼓励您尝试构建将所有内容列出的代码,并通过它们查看您可以获得的见解!
答案 2 :(得分:3)
对于给定集合(或类型,在Haskell中)没有单个monoid。实际上,monoid中的标识不是由定义它的集合决定的,而是由操作(在Haskell中称为mappend
)决定的。例如,可以在添加(带有标识0
)或产品(带有标识1
)时定义整数上的幺半群。
这就是Sum
和Product
类型存在的原因:因为在Monoid
集上有Num a => a
类型类的多种可能实现,我们更喜欢换行将其转换为newtype
并在包装类型上定义Monoid
实现。
Bool
类型有All
类似的结构,在{(1 {1}})的联合((&&)
)下的布尔值为monoid,标识为True
,Any
,在具有身份(||)
的分离(False
)下的布尔值上的幺半群。事实上,布尔可以在许多其他操作(例如XOR和XNOR门)上形成幺半群。
由于Bull
类型与Bool
类型同构(两者都有两个无效的构造函数),因此您可以通过Monoid
上Bool
的实现来激发自己的灵感,但是我们无法通过进一步的背景来确定哪种实现最适合您的情况。
另外,正如Anton Xue所提到的,即使你能为Bull
定义一个幺半群,它真的有意义吗?你的类型应该代表什么?