Haskell 举例说明范畴、幺半群和单子?

Haskell 举例说明范畴、幺半群和单子?,haskell,monads,category-theory,monoids,Haskell,Monads,Category Theory,Monoids,我对这三个概念感到非常困惑 有没有简单的例子来说明两者之间的区别 范畴,幺半群和单子 如果有这些抽象概念的说明,那将非常有帮助。这可能不是您要寻找的答案,但无论如何,这就是您想要的: 看待monads&co.的一种非常扭曲的方式。 看待这些抽象概念的一种方法是将它们与基本概念联系起来,例如普通的列表处理操作。那么你可以说, 类别概括了()操作 幺半群泛化了(++)操作 函子泛化了map操作 应用函子概括了zip(或zipWith)操作 monad泛化了concat操作 类别 类别由一组(或一

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

有没有简单的例子来说明两者之间的区别 范畴,幺半群和单子


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

这可能不是您要寻找的答案,但无论如何,这就是您想要的:

看待monads&co.的一种非常扭曲的方式。 看待这些抽象概念的一种方法是将它们与基本概念联系起来,例如普通的列表处理操作。那么你可以说,

  • 类别概括了
    ()
    操作
  • 幺半群泛化了
    (++)
    操作
  • 函子泛化了
    map
    操作
  • 应用函子概括了
    zip
    (或
    zipWith
    )操作
  • monad泛化了
    concat
    操作
类别 类别由一组(或一类)对象和一组箭头组成,每个箭头连接两个对象。此外,对于每个对象,应该有一个标识箭头将该对象连接到自身。此外,如果有一个箭头(
f
)在一个对象上结束,另一个箭头(
g
)从同一个对象开始,那么也应该有一个称为
g的复合箭头。f

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

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

简言之,Haskell base中的类别是行为类似于函数的东西,也就是说,您可以使用广义版本的
(..
)一个接一个地放置

幺半群 幺半群是具有单位元素和关联运算的集合。这在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 = (++)
幺半群用于“组合”和积累事物。例如,可以使用函数
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
操作的东西。函子一次映射一个一般结构,但使用应用函子可以将两个或多个结构压缩在一起。对于最简单的示例,您可以使用应用程序将
Maybe
类型中的两个整数压缩在一起:

pure (+) <*> Just 1 <*> Just 2  -- gives Just 3
应用程序不仅适用于列表,还适用于各种结构。此外,
pure
()
的巧妙技巧将压缩推广到任何数量的参数。要了解其工作原理,请检查以下类型,同时保留部分应用功能的概念:

instance (Functor f) => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
这与使用单子作为构造计算的手段有什么关系?考虑一个玩具例子,你从两个数据库中连续查询两次。第一个查询返回一些键值,您希望使用这些键值进行另一次查找。这里的问题是第一个值被包装在
Maybe
中,因此您不能直接使用它进行查询。相反,可能是一个
函子
,您可以用新查询代替
fmap
返回值。这将为您提供两个嵌套的
值,可能类似于上面的
值。另一个查询将导致三层
可能
s。这将很难编程,但是一元的
连接
为您提供了一种扁平化此结构的方法,并且只使用单个级别的
可能
s


(我想在这篇文章变得有意义之前,我会对它进行大量的编辑。)

我认为要理解单子,需要使用bind操作符(
>=
)。 受影响的沉重[不要阅读monad教程。)

我的小戏如下:

1.Concat getLine和putStrLn 改编自

签名如下:

Prelude> :t getLine
getLine :: IO String
Prelude> :t (\a -> putStrLn a)
(\a -> putStrLn a) :: String -> IO ()
Prelude> :t f
f :: IO ()
结果:可以看到
(>>=)的部分内容:Monad m=>ma->(a->mb)->mb
签名

2.海螺可能的 改编自

结果:

3.将bind视为加入计算 …如中所述


你读了吗?谢谢你的建议。我应该先读一读。真的。
mempty=0
应该是
mempty=Sum 0
。我喜欢这个答案,但你可能想指出
[]的标准
Applicative
实例
不是zippy,而是笛卡尔积。不太有用的zippy list monad只在长度一致的列表上有效,并且它的
join
是对角的。@C.A.McCann应用函数在另一种意义上是“zippy”,从拥有方法的意义上讲,不是吗
fzip::(fa,fb)->f(A,b)
,将两个
f
组合成一个。(cf.)@WillNess:通常,短语“zippy
Applicative
”表示一个作为结构
instance (Functor f) => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
join (Just (Just 42)) -- gives Just 42
join (Just (Nothing)) -- gives Nothing
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 ()
Prelude> g x = if (x == 0) then Nothing else Just (x + 1)
Prelude> Just 0 >>= g
Nothing
Prelude> Just 1 >>= g
Just 2