简单的例子来说明类别,Monoid和Monad?
我对这三个概念非常困惑。
有没有简单的例子来说明Category,Monoid和Monad之间的区别?
如果有这些抽象概念的例证,这将是非常有帮助的。
这可能不是你正在寻找的答案,但在这里你去:
一个非常扭曲的方式来看monads&co。
看这种抽象概念的一种方法是将它们与基本概念(如普通列表处理操作)联系起来。 那么,你可以这样说,
- 一个类别概括
(.)
操作。 - 一个monoid推广
(++)
操作。 - 函子概括了
map
操作。 - 应用函子概括了
zip
(或zipWith
)操作。 - monad概括了
concat
操作。
一个类别
一个类别由一组(或一类)对象和一堆箭头组成,每个箭头连接两个对象。 另外,对于每个对象,应该有一个标识箭头连接这个对象到自己。 此外,如果有一个箭头( f
)结束于一个对象,另一个( g
)从同一个对象开始,那么也应该有一个称为g . f
的复合箭头g . f
g . f
。
在Haskell中,这被build模为一个typestypes,它将Haskelltypes表示为对象。
class Category cat where id :: cat aa (.) :: cat bc -> cat ab -> cat ac
一个类别的基本例子是function。 每个函数都连接两种types,对于所有types,都有函数id :: a -> a
,将types(和值)连接到自身。 function的组成是普通的function组成。
简而言之,Haskell库中的类别就像函数一样 ,也就是说你可以用(.)
的通用版本一个接一个。
一个Monoid
幺半群是具有单位元素和关联操作的集合。 这在Haskell中被模拟为:
class Monoid a where mempty :: a mappend :: a -> a -> a
monoids常见的例子包括:
- 整数集合,元素0和操作
(+)
。 - 正整数集合,元素1和操作
(*)
。 - 所有列表的集合,空列表
[]
和操作(++)
。
这些在Haskell中被模拟为
newtype Sum a = Sum {getSum :: a} instance (Num a) => Monoid (Sum a) where mempty = Sum 0 mappend (Sum a) (Sum b) = Sum (a + b) instance Monoid [a] where mempty = [] mappend = (++)
幺半群是用来“结合”和积累的东西。 例如,可以使用函数mconcat :: Monoid a => [a] -> a
来将总和列表减less为单一和,或将嵌套列表减less到平面列表中。 把这看作是(++)
或(+)
操作的一种泛化,它以某种方式“合并”了两件事物。
一个Functor
Haskell中的一个函子是一个直接推广map :: (a->b) -> [a] -> [b]
。 它不是映射到列表上,而是映射到某个结构上 ,如列表,二叉树甚至IO操作。 仿函数是这样build模的:
class Functor f where fmap :: (a->b) -> fa -> fb
将此与法线map
函数的定义进行对比。
一个适用的函子
应用函子可以看作是带有广义zipWith
操作的东西。 函子映射在一般的结构上,但是用一个应用函数,你可以将两个或多个结构压缩在一起。 举个最简单的例子,你可以使用Applicatives将Maybe
types中的两个整数压缩在一起:
pure (+) <*> Just 1 <*> Just 2 -- gives Just 3
注意结构会影响结果,例如:
pure (+) <*> Nothing <*> Just 2 -- gives Nothing
将其与通常的zipWith
函数进行比较:
zipWith (+) [1] [2]
而不仅仅是列表,适用于各种结构的应用程序。 另外,使用pure
和(<*>)
的巧妙技巧概括了压缩以处理任意数量的参数。 要看看它是如何工作的,请检查以下types,同时保留部分应用函数的概念:
instance (Functor f) => Applicative f where pure :: a -> fa (<*>) :: f (a -> b) -> fa -> fb
还要注意fmap
和(<*>)
之间的相似性。
Monad
Monad经常被用来模拟不同的计算上下文,比如非确定性或者副作用计算。 由于已经有太多的monad教程,我只是推荐最好的一个 ,而不是写另一个。
关于普通的列表处理函数,monad将函数concat :: [[a]] -> [a]
概括为除了列表之外的许多其他种类的结构。 作为一个简单的例子,monadic操作join
可以用来拼合嵌套的Maybe
值:
join (Just (Just 42)) -- gives Just 42 join (Just (Nothing)) -- gives Nothing
这与使用Monad作为构造计算的手段有什么关系? 考虑一个玩具的例子,你从某个数据库连续进行两次查询。 第一个查询返回一些关键值,您希望进行另一个查询。 这里的问题是,第一个值被封装在Maybe
,所以你不能直接查询。 相反,也许是一个Functor
,你可以用新的查询来fmap
返回值。 这会给你两个像上面嵌套Maybe
值。 另一个查询会导致Maybe
的三个层次。 这将是很难编程,但一元join
给你一个扁平化这个结构的方法,只用一个单一的水平。
(我想我会在编辑这篇文章之前有很多意义..)