50

我对这三个概念感到非常困惑。

有没有简单的例子来说明 Category 、 Monoid 和 Monad 之间的区别?

如果有这些抽象概念的说明,那将非常有帮助。

4

2 回答 2

96

这可能不是您要寻找的答案,但无论如何您都可以这样做:

看待 monads & co 的一种非常歪曲的方式。

查看此类抽象概念的一种方法是将它们与基本概念联系起来,例如普通的列表处理操作。那么,你可以这么说,

  • 类别概括了(.)操作。
  • 一个幺半群概括了这个(++)操作。
  • 函子概括了该map操作。
  • 应用函子泛化了zip(或zipWith)操作。
  • 一个单子概括了这个concat操作。

一个类别

一个类别由一组(或一类)对象和一组箭头组成,每个箭头连接两个对象。此外,对于每个对象,应该有一个标识箭头将这个对象连接到它自己。此外,如果有一个箭头 ( f) 结束于一个对象,另一个 ( g) 从同一对象开始,则还应该有一个复合箭头,称为g . f

在 Haskell 中,这被建模为一个类型类,将 Haskell 类型的类别表示为对象。

 class Category cat where
  id :: cat a a
  (.) :: cat b c -> cat a b -> cat a c

类别的基本示例是函数。每个函数连接两种类型,对于所有类型,都有一个函数id :: a -> a将类型(和值)连接到自身。函数的组合是普通的函数组合。

简而言之,Haskell 基础中的类别是行为类似于函数的事物,即您可以将(.).

一个 Monoid

幺半群是具有单位元素和关联操作的集合。这在 Haskell 中被建模为:

class Monoid a where
  mempty  :: a
  mappend :: a -> a -> a

类半群的常见示例包括:

  • 整数集、元素 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 = (++)

Monoids 用于“组合”和积累事物。例如,函数mconcat :: Monoid a => [a] -> a, 可用于将总和列表缩减为单个总和,或将嵌套列表缩减为平面列表。将其视为以某种方式“合并”两件事的一种概括(++)或操作。(+)

函子

Haskell 中的函子是一个非常直接地概括操作的东西map :: (a->b) -> [a] -> [b]。它不是映射到列表上,而是映射到一些结构上,例如列表、二叉树,甚至是 IO 操作。仿函数的建模如下:

class Functor f where
  fmap :: (a->b) -> f a -> f b

将此与普通map函数的定义进行对比。

一个应用函子

应用函子可以看作是具有广义zipWith运算的事物。函子一次映射一个通用结构,但使用 Applicative 函子,您可以将两个或多个结构压缩在一起。对于最简单的示例,您可以使用应用程序将类型内的两个整数压缩在一起Maybe

pure (+) <*> Just 1 <*> Just 2  -- gives Just 3

请注意,结构会影响结果,例如:

pure (+) <*> Nothing <*> Just 2  -- gives Nothing

将此与通常的zipWith功能进行对比:

zipWith (+) [1] [2]  

该应用程序不仅适用于列表,还适用于各种结构。此外,巧妙的技巧pure(<*>)推广 zipping 可以处理任意数量的参数。要了解它是如何工作的,请检查以下类型,同时保持部分应用函数的概念:

instance (Functor f) => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

fmap还要注意和之间的相似性(<*>)

一个单子

Monad 通常用于对不同的计算上下文进行建模,例如非确定性或副作用计算。由于 monad 教程已经太多了,我只推荐最好的,而不是写另一个。

与普通的列表处理函数有关,monad 将函数泛化为concat :: [[a]] -> [a]与列表以外的许多其他类型的结构一起工作。举个简单的例子,一元操作join可用于展平嵌套Maybe值:

join (Just (Just 42)) -- gives Just 42
join (Just (Nothing)) -- gives Nothing

这与使用 Monads 作为结构化计算的手段有什么关系?考虑一个玩具示例,您从某个数据库执行两个连续查询。第一个查询返回一些键值,您希望使用它进行另一次查找。这里的问题是第一个值被包裹在里面Maybe,所以你不能直接用它来查询。相反,也许是 a Functor,您可以fmap用新查询代替返回值。这会给你两个Maybe像上面一样的嵌套值。另一个查询将产生三层Maybes。这将很难编程,但是单子join给了你一种方法来扁平化这个结构,并且只使用一个级别的Maybes。

(我想在这篇文章有意义之前,我会对其进行大量编辑..)

于 2013-03-31T06:56:21.650 回答
0

我认为要理解 monad,需要使用绑定运算符 ( >>=)。受 [ http://dev.stephendiehl.com/hask/#eightfold-path-to-monad-satori ] 的影响(不要阅读 monad 教程。)

我的小玩法如下:

1. 连接 getLine 和 putStrLn

改编自http://www.haskellforall.com/2014/10/how-to-desugar-haskell-code.html

Prelude> f = getLine >>= \a -> putStrLn a
Prelude> f
abc
abc
Prelude>

和签名:

Prelude> :t getLine
getLine :: IO String
Prelude> :t (\a -> putStrLn a)
(\a -> putStrLn a) :: String -> IO ()
Prelude> :t f
f :: IO ()

结果:可以看到部分(>>=) :: Monad m => m a -> (a -> m b) -> m b签名。

2. 连接Maybe's

改编自https://wiki.haskell.org/Simple_monad_examples

Prelude> g x = if (x == 0) then Nothing else Just (x + 1)
Prelude> Just 0 >>= g
Nothing
Prelude> Just 1 >>= g
Just 2

结果:fail "zero"Nothing

3. 将绑定视为加入计算

...如https://www.slideshare.net/ScottWlaschin/functional-design-patterns-devternity2018中所述

在此处输入图像描述

于 2019-05-17T19:08:27.497 回答