Monads - Flatten 的目的

Monads - Purpose of Flatten

所以我一直在阅读有关 scala 中的 Monads 的文章,以及与它们的 flatMap 函数和 for 理解相关的所有语法。直觉上,我理解为什么 Monads 需要使用 flatMap 函数的 map 部分,就像我通常在零、一个或多个元素的容器上使用 map 函数和 return是同一个容器,但传入的函数应用于容器的所有元素。 Monad 同样是零个、一个或多个元素的容器。

但是,flatMapflatten 部分的目的是什么?我无法理解它背后的直觉。对我来说,这似乎是额外的样板,需要传递给 flatMap 的所有函数来围绕它们的 return 值创建一个容器/monad,只是让该容器立即被 flatten 部分销毁flatMap。我想不出一个使用 flatMap 的例子,它不能通过简单地替换为 map 来简化。例如:

var monad = Option(5)
var monad2 = None

def flatAdder(i:Int) = Option(i + 1)
def adder(i:Int) = i + 1

// What I see in all the examples
monad.flatMap(flatAdder)
// Option[Int] = Some(6)
monad.flatMap(flatAdder).flatMap(flatAdder)
// Option[Int] = Some(7)
monad2.flatMap(flatAdder)
// Option[Int] = None
monad2.flatMap(flatAdder).flatMap(flatAdder)
// Option[Int] = None

// Isn't this a lot easier?
monad.map(adder)
// Option[Int] = Some(6)
monad.map(adder).map(adder)
// Option[Int] = Some(7)
monad2.map(adder)
// Option[Int] = None
monad2.map(adder).map(adder)
// Option[Int] = None

对我来说,单独使用 map 似乎比 flatMap 更直观和简单,而且 flatten 部分似乎没有增加任何价值。但是,在 Scala 中,重点放在了 flatMap 而不是 map 上,以至于它甚至获得了自己的 for 理解语法,所以很明显我一定遗漏了一些东西。我的问题是:在什么情况下 flatMapflatten 部分实际上有用? flatMapmap 有哪些其他优势?

如果处理的每个元素都可能产生零个或多个元素怎么办?

List(4, 0, 15).flatMap(n => primeFactors(n))
//List(2, 2, 3, 5)

如果你有一个名字,但可能拼写不正确,并且你想要他们的办公室分配,如果他们有的话怎么办?

def getID(name:String): Option[EmployeeID] = ???
def getOffice(id:EmployeeID): Option[Office] = ???

val office :Option[Office] =
  getID(nameAttempt).flatMap(id => getOffice(id))

当你 一个 monad 并且你需要将其内容提供给 monad 生产者时,你使用 flatMap()。您想要结果 Monad[X] 而不是 Monad[Monad[X]].

flatMap 背后的想法是能够获取 M[A] 的值和类型为 A => M[B] 的函数 f 并生成 M[B],如果我们没有办法获得 "flatten" 的值,那么我们最终总是会得到一个 M[M[B]],这是我们大多数时候不想要的。 (是的,有些情况下您可能需要一个 M[M[_]],例如当您想要创建 M[_] 的多个实例时)