混淆了 for-comprehension 到 flatMap/Map 的转换 [英] Confused with the for-comprehension to flatMap/Map transformation
问题描述
我似乎真的不太了解 Map 和 FlatMap.我未能理解的是 for-comprehension 如何是对 map 和 flatMap 的嵌套调用序列.以下示例来自 Scala 中的函数式编程
I really don't seem to be understanding Map and FlatMap. What I am failing to understand is how a for-comprehension is a sequence of nested calls to map and flatMap. The following example is from Functional Programming in Scala
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
f <- mkMatcher(pat)
g <- mkMatcher(pat2)
} yield f(s) && g(s)
翻译成
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] =
mkMatcher(pat) flatMap (f =>
mkMatcher(pat2) map (g => f(s) && g(s)))
mkMatcher 方法定义如下:
The mkMatcher method is defined as follows:
def mkMatcher(pat:String):Option[String => Boolean] =
pattern(pat) map (p => (s:String) => p.matcher(s).matches)
模式方法如下:
import java.util.regex._
def pattern(s:String):Option[Pattern] =
try {
Some(Pattern.compile(s))
}catch{
case e: PatternSyntaxException => None
}
如果有人能在这里阐明使用 map 和 flatMap 背后的基本原理,那就太好了.
It will be great if someone could shed some light on the rationale behind using map and flatMap here.
推荐答案
TL;DR 直接看最后的例子
我会试着回顾一下.
定义
for
推导式是以一种易于阅读和推理的方式组合 flatMap
和 map
的语法快捷方式.
The for
comprehension is a syntax shortcut to combine flatMap
and map
in a way that's easy to read and reason about.
让我们稍微简化一下,假设提供上述两种方法的每个 class
都可以称为 monad
,我们将使用符号 M[A]
表示具有内部类型 A
的 monad
.
Let's simplify things a bit and assume that every class
that provides both aforementioned methods can be called a monad
and we'll use the symbol M[A]
to mean a monad
with an inner type A
.
示例
一些常见的 monad 包括:
Some commonly seen monads include:
List[String]
其中M[X] = 列表[X]
A = String
M[X] = Option[X]
A = Int
M[X] = Future[X]
A = (String => Boolean)
地图和平面地图
定义在一个通用的 monad
M[A]
Defined in a generic monad
M[A]
/* applies a transformation of the monad "content" mantaining the * monad "external shape" * i.e. a List remains a List and an Option remains an Option * but the inner type changes */ def map(f: A => B): M[B] /* applies a transformation of the monad "content" by composing * this monad with an operation resulting in another monad instance * of the same type */ def flatMap(f: A => M[B]): M[B]
例如
val list = List("neo", "smith", "trinity") //converts each character of the string to its corresponding code val f: String => List[Int] = s => s.map(_.toInt).toList list map f >> List(List(110, 101, 111), List(115, 109, 105, 116, 104), List(116, 114, 105, 110, 105, 116, 121)) list flatMap f >> List(110, 101, 111, 115, 109, 105, 116, 104, 116, 114, 105, 110, 105, 116, 121)
用于表达
表达式中使用
<-
符号的每一行都被转换为flatMap
调用,除了最后一行被转换为结束map
调用,其中左侧的绑定符号"作为参数传递给参数函数(我们之前称为f: A => M[B]代码>):
Each line in the expression using the
<-
symbol is translated to aflatMap
call, except for the last line which is translated to a concludingmap
call, where the "bound symbol" on the left-hand side is passed as the parameter to the argument function (what we previously calledf: A => M[B]
):
// The following ... for { bound <- list out <- f(bound) } yield out // ... is translated by the Scala compiler as ... list.flatMap { bound => f(bound).map { out => out } } // ... which can be simplified as ... list.flatMap { bound => f(bound) } // ... which is just another way of writing: list flatMap f
只有一个
<-
的 for 表达式被转换为map
调用,并将表达式作为参数传递:A for-expression with only one
<-
is converted to amap
call with the expression passed as argument:// The following ... for { bound <- list } yield f(bound) // ... is translated by the Scala compiler as ... list.map { bound => f(bound) } // ... which is just another way of writing: list map f
进入正题
如您所见,map
操作保留了原始 monad
的形状",因此 yield
表达式也是如此:一个List
仍然是一个List
,内容由yield
中的操作转换.
As you can see, the map
operation preserves the "shape" of the original monad
, so the same happens for the yield
expression: a List
remains a List
with the content transformed by the operation in the yield
.
另一方面,for
中的每个绑定行只是连续的monads
的组合,必须展平"以保持单个外部形状".
On the other hand each binding line in the for
is just a composition of successive monads
, which must be "flattened" to maintain a single "external shape".
假设每个内部绑定都被转换为一个 map
调用,但右侧是相同的 A =>;M[B]
函数,你最终会得到一个 M[M[B]]
用于理解中的每一行.
整个 for
语法的意图是轻松地扁平化"连续的一元操作的串联(即提升"一个一元形状"中的值的操作:A => M[B]
),加上最后的 map
操作,可能 执行结束转换.
Suppose for a moment that each internal binding was translated to a map
call, but the right-hand was the same A => M[B]
function, you would end up with a M[M[B]]
for each line in the comprehension.
The intent of the whole for
syntax is to easily "flatten" the concatenation of successive monadic operations (i.e. operations that "lift" a value in a "monadic shape": A => M[B]
), with the addition of a final map
operation that possibly performs a concluding transformation.
我希望这解释了翻译选择背后的逻辑,它以一种机械的方式应用,即:n
flatMap
嵌套调用由单个 map
调用.
I hope this explains the logic behind the choice of translation, which is applied in a mechanical way, that is: n
flatMap
nested calls concluded by a single map
call.
人为的说明性示例
旨在展示 for
语法
case class Customer(value: Int)
case class Consultant(portfolio: List[Customer])
case class Branch(consultants: List[Consultant])
case class Company(branches: List[Branch])
def getCompanyValue(company: Company): Int = {
val valuesList = for {
branch <- company.branches
consultant <- branch.consultants
customer <- consultant.portfolio
} yield (customer.value)
valuesList reduce (_ + _)
}
你能猜出valuesList
的类型吗?
前面已经说过,monad
的形状是通过推导保持的,所以我们从company.branches
中的一个List
开始,并且必须以 List
结尾.
内部类型改为更改并由 yield
表达式确定:即 customer.value: Int
As already said, the shape of the monad
is maintained through the comprehension, so we start with a List
in company.branches
, and must end with a List
.
The inner type instead changes and is determined by the yield
expression: which is customer.value: Int
valueList
应该是一个 List[Int]
这篇关于混淆了 for-comprehension 到 flatMap/Map 的转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!