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和运算
李>(*)
- 所有列表的集合,空列表
,以及操作[]
(++)
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:通常,短语“zippyApplicative
”表示一个作为结构
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