我对数学概念属性符号把握得不好, 所以了解比较慢,
这篇文章是从概念性的内容去了解 Monad, 不准确, 可能具体到数学概念也不精确.
然而心愿提供一个比拟直观的形式去理解, Monad 是怎么来的?
概念上简略说 Monad 是 ” 自函子领域上的一个幺半群 ”.
新概念很多, 函子, 自函子, 领域, 半群, 幺半群.
模糊地讲, 这些就是数学的概念, 汇合啦, 汇合元素间的映射啦, 单位元啦,
Monad 概念
含糊了解的话, 函子能够当做是函数, a -> b
的映射, 当然也能够比函数更形象点的货色,
而后 ” 自函子 ”, 波及到类型的概念, 函子从一个汇合 A 到另一个汇合 B,
但咱们把程序所有货色都放在一起的话, 函子能够认为是从 A 到 A 本人了, 所以是 ” 自函子 ”.
“ 领域 ” 我解释不来, 大抵是这些函子的形成和关系, 具体看何幻的文章.
从后面这部分看, Haskell 把程序当成各种汇合还有映射来对待了,
程序中, 无论值的变动, 甚至副作用的变动, 全都纳入到领域里边来了解.
而后幺半群呢? 要了解这些概念, 就要晓得相干的几个概念,
- 原群(Magma)
一个汇合, 而后汇合上的元素的二元操作, 操作后果都在这个汇合外部,
一个例子, 比方 {true false}
, 还有二元操作 and
or
,
任何二元操作的后果都在汇合内.
- 半群(Semigroup)
半群在原群的根底上减少了一个条件, 满足结合律:
比方所有非空的字符串的汇合, 以及 concat
操作."a" `concat` "b"
失去 "ab"
还在汇合内,
而后 ("a" `concat` "b") `concat` "c"
失去 "abc"
,
而后 "a" `concat` ("b" `concat` "c")
失去 "abc"
,
两者是等价的, 满足结合律.
- 幺半群(Monoid)
幺半群, 在半群的根底上减少一个条件, 存在幺元,
幺元跟任何元素 a
联合失去的都是 a
自身,
例子的话, 在下面这个非空字符串的例子当中再加上空字符串,
那么 ""`concat`"a"
失去 "a"
,
而后 "a"`concat`""
失去 "a"
,
两者等价的, ""
就是这个汇合的幺元.
- 群(Group)
群在幺半群的根底上在加上了一个条件, 存在逆元,
一个例子比方整数的汇合, 其中 (x + y) + z = x + (y + z)
满足结合律,x + 0 = 0 + x
存在幺元,x + (-x) = 0
存在逆元,
所以整数的汇合就是一个幺半群了.
当然这个叙述就不准确了, 但你大抵应该晓得幺半群对应什么了,
汇合, 二元操作闭合, 交换律, 幺元.
特地是字符串这个例子, 能够看到程序当中显著会有很多这样的对应,
咱们大量应用数组, 数组也是满足这几个条件的,
还是那个 concat
操作, 闭合, 交换律, 幺元 ([]
), 都是成立,
而后数值计算, +
, *
这两个操作, 闭合, 交换律, 幺元, 也是存在的.
而后须要绕过来了解一下了, 对于函数, 对于副作用, 是不是也是幺半群?
函数吧, 有 f g h
三个函数, 而后有个复合函数的操作 compose
,(f `compose` g) `compose` h
是一个,f `compose` (g `compose` h)
是另一个,compose
是左边函数先计算的, 所以总是当初 h(x)
先计算, 最终是 f(g(h(x)))
.
这个是结合律.
能够直观了解一下, 函数联合常识联合的函数, 最终调用还是固定的程序的.
另外幺元就是 function(x){return x}
这个函数了. 左右都一样.
而后副作用呢, 咱们把副作用拿函数包一下, function(){ someEffect() }
,
而后依照函数 compose
的操作, 还是有结合律的,
而后咱们定义一个副作用为空的 Unit
操作, 能够作为幺元,
因为是空的副作用, 所以放在右边放在左边都是不影响的.
所以函数, 副作用, 这些也还是能依照幺半群来了解和束缚的.
这样想上来, 程序里边大量的内容都是能够套用幺半群去了解了.
而要害地位就是结合律还有幺元, 对于函数来说就是函数复合还有 (x) => x
函数.
既然幺半群是程序当中普遍存在的构造, 也就能间接地承受这个概念了.
而后 ” 自函子领域上的幺半群 ”, 也就是说限定在 ” 自函子 ” 而不是 ” 一般汇合元素 ” 的幺半群了.
这个不能精确形容, 然而应该足够提供一些观点上的了解了, Monad 是怎么进去的 …
Monad class
在 Haskell 里定义又要再另外了解了, 首先对幺半群来说还是清晰的,
而交换律作为 law 没有写在 class 的定义当中了,
class Monoid m where
-- 定义元素
mempty :: m
-- 定义闭合二元操作
mappend :: m -> m -> m
-- 定义多个元素的联合, 默认是用的 List 示意
-- 满足结合律, 所以 foldr 是从左边开始联合的, 跟右边联合成果统一
mconcat :: [m] -> m
mconcat = foldr mappend mempty
对应的 Monad 版本, 也有着跟 Monoid 对应的一些函数的构造,
class Monad m where
-- 定义幺元
return :: a -> m a
-- 对应上边的二元操作 mappend
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
-- 对应上边的多个元素的组合 mconcat
join :: m => m (m a) -> m a
这边容易一致的用法呈现了, 首先幺元的定义:
-- 在 Monoid 当中是
mappend :: m
-- 在 Monad 当中是
return :: a -> m a
-- 或者有的中央用是 pure 的名称
pure :: a -> m a
-- 那么, Monoid 中的二元操作
mappend :: m -> m -> m
-- 到了 Monad, 应该是
mappend :: m a -> m a -> m a
不过咱们理论看到的是两个类型变量, a
b
:
-- 蕴含从 a 到 m b 态射的一个过程
(>>=) :: m a -> (a -> m b) -> m b
-- 蕴含 a 也蕴含 b 然而 a 被抛弃的过程, 比拟可能是通过副作用抛弃了
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
这个认真想想也能够了解, 比方 List Int
, 整数的数组,
通过映射之后, 可能还是 List Int
, 也可能通过 length
失去 Int
,
不过在 m a
这个类型束缚当中, 不会是纯的 Int
了,
可能是 List String
, 比方 [1,2,3,4].map(JSON.stringify)
,
可能是 List Boolean
, 比方 [1,2,3,4].map(x => x > 0)
,
总之这个中央因为态射的存在而变动了.
至于为什么要这样定义, 如果说 a -> m b
这个过程不须要跟 m 产生作用,
那么咱们用态射, 间接用 Functor 就能达成了,
class Functor f where
fmap :: (a -> b) -> f a -> f b
然而存在状况, 就是须要跟 m 产生作用的, 比方 IO, 就必然会,
而后是 flatMap 的状况, 计算过程要跟 List 这个结构器产生作用:
Prelude> (>>=) [1,2,3,4] (\x -> [0..x])
[0,1,0,1,2,0,1,2,3,0,1,2,3,4]
IO Monad 的特殊性在于支流语言习惯性不去管 IO,
然而依照 Monoid 这套了解下来, IO 的确用是这样的构造.
其余
里边的概念都太形象了, 特地是领域相干的, 这个写得不太能自圆其说.