我似乎真的不了解Map和FlatMap。我无法理解的是for-comprehension是如何嵌套调用map和flatMap的。以下示例来自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方法定义如下:
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背后的理由,那将会很棒。
答案 0 :(得分:180)
TL; DR直接进入最后一个例子
我会试着回顾一下
<强>解释强>
for
理解是一种语法快捷方式,可以将flatMap
和map
结合起来,使其易于理解和推理。
让我们稍微简化一下,假设提供上述两种方法的每个class
都可以称为monad
,我们将使用符号M[A]
来表示monad
}内部类型A
。
<强>实施例强>
一些常见的monads
List[String]
其中
M[X] = List[X]
A = String
Option[Int]
其中
M[X] = Option[X]
A = Int
Future[String => Boolean]
其中
M[X] = Future[X]
A = (String => Boolean)
地图和flatMap
在通用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]
e.g。
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]
):
// 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-expression将转换为map
调用,并将表达式作为参数传递:
// 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
中的操作转换。
另一方面,for
中的每个绑定线只是连续monads
的组合,必须“展平”才能保持单一的“外部形状”。
假设片刻已将每个内部绑定转换为map
调用,但右侧是相同的A => M[B]
函数,您最终会得到M[M[B]]
个理解中的一句话
整个for
语法的意图是轻松“扁平化”连续monadic操作的连接(即“提升”“monadic形状”值的操作:A => M[B]
),并添加可能执行结束转换的最终map
操作。
我希望这能解释翻译选择背后的逻辑,它以机械方式应用,即:n
flatMap
嵌套调用由单个map
调用结束。< / p>
一个人为的说明性例子
意味着显示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)
valueList reduce (_ + _)
}
您能猜出valuesList
的类型吗?
如前所述,monad
的形状是通过理解来维持的,因此我们从List
中的company.branches
开始,并且必须以List
结束。
内部类型改为由yield
表达式确定:customer.value: Int
valueList
应为List[Int]
答案 1 :(得分:5)
我不是一个scala mega mind,所以请随意纠正我,但这就是我向自己解释flatMap/map/for-comprehension
传奇的方式!
要理解for comprehension
及其对scala's map / flatMap
的翻译,我们必须采取一些小步骤,并了解作曲部分 - map
和flatMap
。但是scala's flatMap
map
而flatten
与for-comprehension / flatMap / map
你问自己!如果是这样,为什么许多开发人员发现很难掌握它或map
。好吧,如果您只是查看scala的flatMap
和M[B]
签名,您会看到它们返回相同的返回类型A
并且它们使用相同的输入参数map
(至少他们所采取的功能的第一部分)如果这样做会产生什么影响呢?
我们的计划
flatMap
。for comprehension
。map[B](f: (A) => B): M[B]
。“Scala的地图
scala地图签名:
A
但是当我们看到这个签名时,有很大一部分缺失了,而且它是A
来自哪里?我们的容器属于M[A]
类型,因此在容器的上下文中查看此函数非常重要 - List
。我们的容器可以是A
map
类型的项目,我们的A
函数会使用一个函数将B
类型的每个项目转换为B
类型,然后它返回M[B]
(或M[A]: // We are in M[A] context.
map[B](f: (A) => B): M[B] // map takes a function which knows to transform A to B and then it bundles them in M[B]
)
考虑到容器,让我们写地图的签名:
M[B]
请注意非常重要的关于地图的事实 - 它会在输出容器map
中捆绑自动,而您无法控制它。让我们再次强调它:
M[A]
为我们选择输出容器,它将与我们处理的源容器相同,因此对于M
容器,我们只为{{{{}}容器获取相同的B
容器1}} M[B]
,别无其他!map
为我们执行此容器化,我们只是提供从A
到B
的映射,并将其放在M[B]
的框中,将其放入框中对我们来说!您发现您没有指定如何containerize
您刚刚指定的项目如何转换内部项目。由于我们对M
和M[A]
都有相同的容器M[B]
,这意味着M[B]
是同一个容器,这意味着如果你有List[A]
那么你就要去拥有List[B]
,更重要的是map
正在为你做这件事!
现在我们处理了map
,让我们继续flatMap
。
Scala的flatMap
让我们看看它的签名:
flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]
你看到在flatMap中从地图到flatMap
的巨大差异我们正在为它提供的功能不仅仅是从A to B
转换而且还将它包含在M[B]
中。
为什么我们关心谁进行集装箱化?
那么为什么我们如此关注map / flatMap的输入函数是否将容器化成M[B]
或地图本身为我们进行了容器化?
您在for comprehension
的上下文中看到发生的事情是for
中提供的项目的多次转换,因此我们正在为装配线中的下一个工作人员提供确定打包。想象一下,我们有一条装配线,每个工人都对产品做了一些事情,只有最后一个工人将它包装在容器中!欢迎来到flatMap
这就是它的目的,在map
每个工人完成项目工作后也将其打包,这样你就可以将容器放在容器上。
强大的理解力
现在让我们考虑一下我们上面所说的内容:
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
f <- mkMatcher(pat)
g <- mkMatcher(pat2)
} yield f(s) && g(s)
我们在这里得到了什么:
mkMatcher
返回container
容器包含的函数:String => Boolean
<-
多个flatMap
转换为f <- mkMatcher(pat)
,则除了最后一个规则之外,规则就是这样。sequence
是assembly line
中的第一个(想想f
),我们想要的只是取map
并将其传递给装配线中的下一个工作人员,我们让装配线中的下一个工人(下一个功能)能够确定我们项目的包装背面,这就是为什么最后一个功能是g <- mkMatcher(pat2)
。最后map
将使用map( g =>
这是因为它在装配线上的最后一个!所以它可以用g
进行最后的操作,是的!拉出f
并使用flatMap
已经从容器中取出的f
,因此我们最终得到了第一个:
mkMatcher(pat)flatMap(f //拉出f函数将项目提供给下一个装配线工人(你看到它有权访问map
,并且不打包回来我的意思是让地图确定包装让下一个装配线工人确定容器。
mkMatcher(pat2)map(g =&gt; f(s)...))//因为这是装配线中的最后一个函数,我们将使用map并将g拉出容器并返回包装,它的{{1}}和这个包装会一路飙升,成为我们的包裹或我们的容器,是啊!
答案 2 :(得分:4)
理由是将monadic操作链接起来,提供正确的“快速失败”错误处理。
实际上非常简单。 mkMatcher
方法返回Option
(Monad)。
mkMatcher
(monadic操作)的结果是None
或Some(x)
。
将map
或flatMap
函数应用于None
始终会返回None
- 作为参数传递给map
和flatMap
的函数}未被评估。
因此,在您的示例中,如果mkMatcher(pat)
返回None,则应用于它的flatMap将返回None
(第二个monadic操作mkMatcher(pat2)
将不会执行)和final { {1}}将再次返回map
。
换句话说,如果for comprehension中的任何操作返回None,则表示快速失败,其余操作不会执行。
这是一种错误处理的monadic风格。命令式样式使用异常,基本上是跳转(到catch子句)
最后一点:None
函数是一种将命令式样式错误处理(patterns
... try
)“转换”为使用{的monadic样式错误处理的典型方法{1}}
答案 3 :(得分:1)
这可以被翻译为:
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
f <- mkMatcher(pat) // for every element from this [list, array,tuple]
g <- mkMatcher(pat2) // iterate through every iteration of pat
} yield f(s) && g(s)
运行此命令以更好地查看其扩展
的方式def match items(pat:List[Int] ,pat2:List[Char]):Unit = for {
f <- pat
g <- pat2
} println(f +"->"+g)
bothMatch( (1 to 9).toList, ('a' to 'i').toList)
结果是:
1 -> a
1 -> b
1 -> c
...
2 -> a
2 -> b
...
这类似于flatMap
- 遍历pat
中的每个元素,并将map
元素pat2
传递给{{1}}
答案 4 :(得分:0)
首先,mkMatcher
返回一个签名为String => Boolean
的函数,这是一个只运行Pattern.compile(string)
的常规java过程,如pattern
函数所示。
然后,看看这一行
pattern(pat) map (p => (s:String) => p.matcher(s).matches)
map
函数应用于pattern
的结果,即Option[Pattern]
,因此p
中的p => xxx
只是您编译的模式。因此,给定模式p
,构造一个新函数,它接受一个字符串s
,并检查s
是否与模式匹配。
(s: String) => p.matcher(s).matches
注意,p
变量与编译模式有关。现在,很明显String => Boolean
如何构建带有签名mkMatcher
的函数。
接下来,让我们查看bothMatch
函数,该函数基于mkMatcher
。为了说明bothMathch
的工作原理,我们先来看看这部分:
mkMatcher(pat2) map (g => f(s) && g(s))
由于我们从String => Boolean
获得了一个带有签名mkMatcher
的函数,在此上下文中为g
,g(s)
等同于Pattern.compile(pat2).macher(s).matches
,如果pat2
返回String s匹配模式f(s)
。那么g(s)
如何与mkMatcher
相同,唯一的区别是,flatMap
的第一次调用使用的是map
,而不是mkMatcher(pat2) map (g => ....)
,为什么?由于Option[Boolean]
会返回Option[Option[Boolean]]
,如果您同时使用map
,则会得到嵌套结果{{1}},这不是您想要的。