梅贾德斯不是照人类传统的时间来记戴,而是着眼在一个世纪发生的生活故事,一切同时存在於一瞬间。
-- 加布列·贾西亚·马奎斯, 百年孤寂
又一次来到墙的前面。即使它还是半透明的,但却依然能感觉到那个悠久而质朴的感觉。慢慢延着墙散步,看着其上各式各样的痕迹,可以感受到有多少想穿过它的尝试。
但那座墙上是有个门的。而我现在看得到了。
我朝着门走去,而那本书也跟了上来:
Monad 的实作:
class Applicative m => Monad (m :: * -> *) where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a {-# MINIMAL (>>=) #-}
Monad 的法则:
- 单位元素
- 结合律
在谈 Monad 是什麽之前,我认为更加重要的,是先确定一下 Monad 不是什麽:
在参悟的过程中,觉得有点怀疑的时候,可以回头来对照一下这张表。
fmap
的函式也是回传一个串列时让我们回到串列那个大家都爱用的 map
来看一下:
-- Haskell 语法
f = (+1)
map f [1, 2, 3] -- => [2, 3, 4]
那麽如果我们有个函式 g
,输入一个数字,就会回传一个串列的话:
-- Haskell 语法
g x = [x, x + 1, x + 2]
g 10 -- => [10, 11, 12]
那麽我们用 g
这个函式,对一个串列进行 map
时,会回传……串列包着的串列:
-- Haskell 语法
map g [1, 2, 3] -- => [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
很多时候,我们会希望上面那个串列的串列,是能自动坍缩成一层的阵列的。如果你很习惯用其它语言里的 map
,那麽你八成也知道有一个这种特性的函式: flat_map
:
# Elixir 语法
g = fn x -> [x, x + 1, x + 2] end
Enum.flat_map([1, 2, 3], g)
# => [1, 2, 3, 2, 3, 4, 3, 4, 5]
而在 Haskell 里,你要用 concat
这个把串列的串列摊平的函式,与 map
一起来做到这样的事:
-- Haskell 语法
concat [[1, 2], [3, 4]] -- => [1, 2, 3, 4]
concat $ map (\x -> [x, x + 1, x + 2]) [1, 2, 3]
-- => [1,2,3,2,3,4,3,4,5]
嗯。这个就是 Monad 的特性。
return
?许多人在看到型别定义里,那个叫 return
的函式时,通常会非常疑惑。因为绝大部份的程序语言里,都把这个字当做是终止目前的计算,并回传结果用的关键字。但是仔细看一下,在 Monad 里,这个 return
只是一个非常普通的函式。而它的型别是:
return :: a -> m a
仔细看一下,它跟 Applicative 里的 pure
基本上是同一个东西。它不会终止什麽计算,也没有什麽特殊的行为。就是个单纯的拿到一个值,把值装进容器里的函式而己。
但是之所以选这个字,是有其用意的,之後我们就会看到了。
当然 Monad 重点并不在於把嵌叠的串列坍成一层而己。如果谈论的对象只是串列的话,那就用 flat_map
,或 concat $ map
来理解就可以了。而 Monad,既然是一个 typeclass ,那麽理解的重心,则是放在坍缩成一层这个概念,以及有哪些容器也能有这个坍缩成一层的特性,接着会延伸出这个特性的不同使用手法。
让我们先来看 Monad 最重要的 >>=
函式,大家称它为 bind
。这个函式先接收一个 Monad f a
,再接收一个函式 (a -> fb)
,最後会回传包在单层容器里的 f (b)
。
为了要看这一系列 typeclass 的函式型别变换,我们把 >>=
改成用方向相反的 =<<
来表示,这个反过来的 =<<
则是一个先接收函式,再接收 Monad 的函式。
另外我们依序列出从函式应用 $
、Functor 的 <$>
(fmap 的中缀形式)、Applicative 的 <*>
与 Monad 的 =<<
的型别,一个个往下排,并加上空白看看:
-- Haskell 语法
$ :: (a -> b) -> a -> b
<$> :: Functor f => (a -> b) -> f a -> f b
<*> :: Applicative f => f (a -> b) -> f a -> f b
(=<<) :: Monad f => (a -> f b) -> f a -> f b
--- 参考用: 原本的 >>= 的型别
(>>=) :: Monad f => f a -> (a -> f b) -> f b
我们之前已经看过,从 $
到 <$>
,第一个参数的函式保持不变,而接收的第二个参数,从处理单纯的值,变成可以处理装在容器里的值,是所谓的升格。
而从 <$>
到 <*>
,第一个参数的函式,也被放到容器里。而我们也说过,其行为可以看做容器里的两个值(一个函式,另一个是引数),用函式呼叫产生结果,而两边的容器外壳,则会像是 Monoid 那样结合 (mappend
) 在一起。
而当我们谈到 =<<
,如果只是单纯的用 fmap
/<$>
将第一个参数的 (a -> f b)
的函式,应用到f a
里的话(注意 a
被装在 f
容器里)。那麽其结果的型别会是:f (f b)
(想一下两层的串列)。而 =<<
的特性,就是它会把这个两层相同的 f
容器坍缩成一层,让结果是 f b
。
是的。 Monad 的本质,就是这样而己。
当我正想再去找万用钥匙时,发现这次门上面的标识不太一样。开孔上方,写着这样的字:
join :: m (m a) -> m a
所以是想要一个可以把双层的容器坍缩成一层容器的函式罗?我仔细想了一下……
[to be continue]
<<: [DAY 30] 复刻 Rails - View 威力加强版 - 2
大家好~ 我是五岁~ 今天来挑战一个树怪吧~!! 目标就是一棵树~ 阿不是...是一个树怪~ 不想要...
在很多很多的前置作业後 今天终於要开始写code了 到此为止我们应该流程画有了 画面流程也有了 今天...
人们如果主动隐瞒某些事 反而会花两倍时间想着那着些事 秘密的问题在於 只要你说出来 他就不再是秘密...
Virtual Judge ZeroJudge 题意 真.排序题 输入数字,按照要求输出排序後的结...
今天介绍插入排序法&快速排序法~~ 主题还是希望围绕在实战刷题,毕竟刷题的时候有需要排序大多...