Monad ---- “the m-word”,“a monoid in the category of endofunctors”.
see A monad is just a monoid in the category of endofunctors, what’s the problem? for fun
在FP的路上,不可避免地会碰到monad这个拦路虎。绕是绕不过去的,那就学咯。
“我看了几十篇关于monad的文章,还是没懂。” – 某不知名FP爱好者
这篇文章也不是silver bullet,我只希望读者在读过以后,对monad能有个大致的、模糊的印象,今后能够持续地从多个角度去审视这个概念,加深认识。
什么是monad
我们在说functor的时候,有一个不那么准确的定义:有map
的,就是functor。
当我们这样定义的时候,我们其实是在泛化map
,将它们的共性抽象出来,这个抽象就是functor。
同样地,flatMap
也出现在很多地方(比如List
,Option
,Future
,Either
等等等),我们自然也想把共性抽象出来,这个抽象,就是monad。所以可以类似地说:有flatMap
的,就是monad。
好吧,如果要正式一点,引用下scala with cats里的定义:monad是一种将计算按顺序排列起来的机制(a mechanism for sequencing computations)。
flatMap
是什么
如果你熟悉flatMap
,可以跳过这一小节。
1 | case class Person(name: String, friends: List[String]) |
对于List[A]
来说,map
和flatMap
的签名,仅仅是传入的f
的返回类型不一样:
1 | def map[A, B](f: A => B): List[B] |
直观来讲,flatMap
将List
内的每一个A,转换成了一个List[B]
,然后将所有的List[B]
,连了起来。
举个栗子🌰
我们知道,Option
可以用来表示结果是否存在。
考虑整数除法:
1 | // not pure, can throw exception |
现在我们不仅要做除法,还要让用户输入这两个值,我们需要一个parseInt
1 | def naiveParseInt(s: String): Int = s.toInt |
现在我们把他们串起来,自然地,我们也需要返回一个Option[Int]
来涵盖结果的成功和失败。
1 | def stringDiv(a: String, b: String): Option[Int] = |
stringDiv
做了这些事情:
parseInt(a)
返回一个None
或者Some(x)
;- 如果是
Some
,将Some
内的值x
取出来,传递给后面的函数; parseInt(b)
返回一个None
或者Some(y)
;- 如果是
Some
,将Some
内的值y
取出来,继续传给后面的函数; - 计算
div(x, y)
,返回最终结果None
或者Some(x)
。
手动使用flatMap
拼接比较繁琐,也不太容易看得清。Scala为我们提供了一个语法糖:for-comprehension。
下面的stringDiv2
与stringDiv是等价的,编译器会帮我们把for-comprehension编译成flatMap
(和map
)的调用链。
1 | def stringDiv2(a: String, b: String): Option[Int] = |
审视一下上面的例子,我们确确实实地将计算给串起来了。
flatMap
起了什么作用呢?
flatMap
将上游Option[Int]
中的值抽出来,执行运算,再塞进Option[Int]
中,传递给下游。
因为div
只能接受Int
,而传给div
的是Option[Int]
,所以我们不能用简单的函数组合来解决问题。
using compose |
using flatMap |
---|---|
naiveParseInt: String => Int |
parseInt: String => Option[Int] |
naiveDiv: (Int, Int) => Int |
div: (Int, Int) => Option[Int] |
(a, b) => naiveDiv(naiveParseInt(a),naiveParseInt(b)) |
see stringDiv2 |
Monad
的定义
在上述的例子中,我们使用flatMap
将Option[Int]
串了起来。
如果我们只有Int
,flatMap
岂不是毫无作用?
没关系,我们还有unit
,可以把一个值A
变成F[A]
,可以视作将一个纯粹的值提升到了monad上下文中。
1 | trait Monad[F[_]] { |
我们说过,monad是特殊的functor。换言之,所有的monad都是functor,不是所有的functor都是monad。
因为我们可以使用unit
和flatMap
实现map
,有了map
,自然就是functor。
1 | def map[A, B](ma: F[A])(f: A => B): F[B] = ??? |
这个实现是显然且唯一的,答案在文章末尾。
小结
这次我们通过一个简单的例子阐述了monad的作用,并写下了monad的一种形式化定义。
接下来,我们还会研究monad定律,研究monad的不同表现形式。
最后,我们会研究大量的monad实例,来加深理解。
Reference
- Monads: Programmer’s Definition
- Monads – Chapter 3 in Scala with Cats
- Monad – Chapter 11 in Functional Programming in Scala
答案:通过unit
和flatMap
实现map
1 | def map[A, B](ma: F[A])(f: A => B): F[B] = |