scala 混淆了 for-comprehension 到 flatMap/Map 的转换
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14598990/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Confused with the for-comprehension to flatMap/Map transformation
提问by sc_ray
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
我似乎真的不理解 Map 和 FlatMap。我未能理解的是 for-comprehension 如何是对 map 和 flatMap 的嵌套调用序列。以下示例来自Scala 中的函数式编程
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
f <- mkMatcher(pat)
g <- mkMatcher(pat2)
} yield f(s) && g(s)
translates to
翻译成
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] =
mkMatcher(pat) flatMap (f =>
mkMatcher(pat2) map (g => f(s) && g(s)))
The mkMatcher method is defined as follows:
mkMatcher 方法定义如下:
def mkMatcher(pat:String):Option[String => Boolean] =
pattern(pat) map (p => (s:String) => p.matcher(s).matches)
And the pattern method is as follows:
模式方法如下:
import java.util.regex._
def pattern(s:String):Option[Pattern] =
try {
Some(Pattern.compile(s))
}catch{
case e: PatternSyntaxException => None
}
It will be great if someone could shed some light on the rationale behind using map and flatMap here.
如果有人能在这里阐明使用 map 和 flatMap 背后的基本原理,那就太好了。
回答by pagoda_5b
TL;DR go directly to the final example
TL;DR 直接看最后的例子
I'll try and recap.
我会试着回顾一下。
Definitions
定义
The forcomprehension is a syntax shortcut to combine flatMapand mapin a way that's easy to read and reason about.
的for理解是一个语法快捷方式相结合flatMap,并map以一种易于阅读和推理。
Let's simplify things a bit and assume that every classthat provides both aforementioned methods can be called a monadand we'll use the symbol M[A]to mean a monadwith an inner type A.
让我们稍微简化一下,假设class提供上述两种方法的每个都可以称为 a monad,我们将使用符号M[A]来表示monad具有内部类型的 a A。
Examples
例子
Some commonly seen monads include:
一些常见的 monad 包括:
List[String]whereM[X] = List[X]A = String
Option[Int]whereM[X] = Option[X]A = Int
Future[String => Boolean]whereM[X] = Future[X]A = (String => Boolean)
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)
map and flatMap
地图和平面地图
Defined in a generic monad M[A]
在通用 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)
for expression
为了表达
Each line in the expression using the
<-symbol is translated to aflatMapcall, except for the last line which is translated to a concludingmapcall, 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 fA for-expression with only one
<-is converted to amapcall 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
表达式中使用
<-符号的每一行都被转换为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 表达式
<-被转换为一个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
Now to the point
现在进入正题
As you can see, the mapoperation preserves the "shape" of the original monad, so the same happens for the yieldexpression: a Listremains a Listwith the content transformed by the operation in the yield.
如您所见,该map操作保留了原始 的“形状” monad,因此yield表达式也会发生同样的情况:aList仍然是 a List,而yield.
On the other hand each binding line in the foris just a composition of successive monads, which must be "flattened" to maintain a single "external shape".
另一方面, 中的每个装订线for只是连续 的组合monads,必须“展平”以保持单个“外部形状”。
Suppose for a moment that each internal binding was translated to a mapcall, 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 forsyntax 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 mapoperation that possiblyperforms a concluding transformation.
假设有一段时间,每个内部绑定都被转换为一个map调用,但右边是相同的A => M[B]函数,你最终会M[M[B]]在推导式中为每一行生成一个。
整个for语法的意图是轻松地“展平”连续的一元操作的串联(即“提升”“一元形状”中的值的操作:)A => M[B],并添加可能执行结束转换的最终map操作。
I hope this explains the logic behind the choice of translation, which is applied in a mechanical way, that is: nflatMapnested calls concluded by a single mapcall.
我希望这解释了翻译选择背后的逻辑,它以一种机械的方式应用,即:nflatMap由单个调用结束的嵌套调用map。
A contrived illustrative example
Meant to show the expressiveness of the forsyntax
一个人为的说明性示例
旨在展示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 (_ + _)
}
Can you guess the type of valuesList?
你能猜出它的类型valuesList吗?
As already said, the shape of the monadis maintained through the comprehension, so we start with a Listin company.branches, and must end with a List.
The inner type instead changes and is determined by the yieldexpression: which is customer.value: Int
前面已经说过, the 的形状monad是通过推导保持的,所以我们从 a Listin开始company.branches,必须以 a 结束List。
内部类型会发生变化,并由yield表达式确定:customer.value: Int
valueListshould be a List[Int]
valueList应该是一个 List[Int]
回答by Tomer Ben David
I'm not a scala mega mind so feel free to correct me, but this is how I explain the flatMap/map/for-comprehensionsaga to myself!
我不是一个超级头脑所以随时纠正我,但这就是我flatMap/map/for-comprehension向自己解释传奇的方式!
To understand for comprehensionand it's translation to scala's map / flatMapwe must take small steps and understand the composing parts - mapand flatMap. But isn't scala's flatMapjust mapwith flattenyou ask thyself! if so why do so many developers find it so hard to get the grasp of it or of for-comprehension / flatMap / map. Well, if you just look at scala's mapand flatMapsignature you see they return the same return type M[B]and they work on the same input argument A(at least the first part to the function they take) if that's so what makes a difference?
要理解for comprehension并将其翻译为scala's map / flatMap我们必须采取小步骤并理解组成部分 -map和flatMap. 但是,不scala's flatMap只是map与flatten你问你自己!如果是这样,为什么这么多开发人员发现很难掌握它或for-comprehension / flatMap / map. 好吧,如果您只查看 Scalamap和flatMap签名,您会发现它们返回相同的返回类型,M[B]并且它们处理相同的输入参数A(至少是它们采用的函数的第一部分),如果是这样的话有什么不同?
Our plan
我们的计划
- Understand scala's
map. - Understand scala's
flatMap. - Understand scala's
for comprehension.`
- 了解 Scala 的
map. - 了解 Scala 的
flatMap. - 了解 Scala 的
for comprehension.`
Scala's map
斯卡拉的地图
scala map signature:
Scala 地图签名:
map[B](f: (A) => B): M[B]
But there is a big part missing when we look at this signature, and it's - where does this Acomes from? our container is of type Aso its important to look at this function in the context of the container - M[A]. Our container could be a Listof items of type Aand our mapfunction takes a function which transform each items of type Ato type B, then it returns a container of type B(or M[B])
但是当我们查看这个签名时,有很大一部分缺失,它是 - 这A是从哪里来的?我们的容器属于类型,A因此在容器的上下文中查看此函数很重要 - M[A]. 我们的容器可以是一个List类型的项目,A我们的map函数采用一个函数将每个类型的项目转换A为 type B,然后它返回一个类型为B(或M[B])的容器
Let's write map's signature taking into account the container:
让我们考虑到容器来编写地图的签名:
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]
Note an extremely highly highly important fact about map- it bundles automaticallyin the output container M[B]you have no control over it. Let's us stress it again:
请注意关于地图的一个极其重要的事实- 它会自动捆绑在M[B]您无法控制的输出容器中。让我们再次强调一下:
mapchooses the output container for us and its going to be the same container as the source we work on so forM[A]container we get the sameMcontainer only forBM[B]and nothing else!mapdoes this containerization for us we just give a mapping fromAtoBand it would put it in the box ofM[B]will put it in the box for us!
map为我们选择输出容器,它与我们处理的源是同一个容器,所以对于M[A]容器,我们只得到相同的M容器BM[B],没有别的!map为我们做这个容器化,我们只是给出一个从A到的映射B,它会把它放在盒子里,M[B]将它放在盒子里给我们!
You see you did not specify how to containerizethe item you just specified how to transform the internal items. And as we have the same container Mfor both M[A]and M[B]this means M[B]is the same container, meaning if you have List[A]then you are going to have a List[B]and more importantly mapis doing it for you!
你看你没有指定containerize你刚刚指定如何转换内部项目的项目。由于我们M为两者配备了相同的容器M[A],M[B]这意味着M[B]是同一个容器,这意味着如果您拥有,List[A]那么您将拥有一个List[B],更重要的map是为您做这件事!
Now that we have dealt with maplet's move on to flatMap.
现在我们已经处理了,map让我们继续flatMap。
Scala's flatMap
Scala 的 flatMap
Let's see its signature:
让我们看看它的签名:
flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]
You see the big difference from map to flatMapin flatMap we are providing it with the function that does not just convert from A to Bbut also containerizes it into M[B].
您会看到从 map 到flatMapflatMap的巨大差异,我们为它提供了不仅可以将A to B其转换为M[B].
why do we care who does the containerization?
为什么我们关心谁来做容器化?
So why do we so much care of the input function to map/flatMap does the containerization into M[B]or the map itself does the containerization for us?
那么为什么我们如此关心 map/flatMap 的输入函数是进行容器化M[B]还是地图本身为我们进行容器化呢?
You see in the context of for comprehensionwhat's happening is multiple transformations on the item provided in the forso we are giving the next worker in our assembly line the ability to determine the packaging. imagine we have an assembly line each worker does something to the product and only the last worker is packaging it in a container! welcome to flatMapthis is it's purpose, in mapeach worker when finished working on the item also packages it so you get containers over containers.
您在所for comprehension发生的事情的上下文中看到的是 中提供的项目的多次转换,for因此我们为装配线中的下一个工人提供了确定包装的能力。想象一下我们有一条装配线,每个工人都对产品做某事,只有最后一个工人将其包装在容器中!欢迎来到flatMap这是它的目的,在map每个工人完成该项目的工作后,还将其打包,以便您获得容器上的容器。
The mighty for comprehension
理解力强者
Now let's looks into your for comprehension taking into account what we said above:
现在让我们考虑到我们上面所说的内容来研究您的理解:
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
f <- mkMatcher(pat)
g <- mkMatcher(pat2)
} yield f(s) && g(s)
What have we got here:
我们在这里得到了什么:
mkMatcherreturns acontainerthe container contains a function:String => Boolean- The rules are the if we have multiple
<-they translate toflatMapexcept for the last one. - As
f <- mkMatcher(pat)is first insequence(thinkassembly line) all we want out of it is to takefand pass it to the next worker in the assembly line, we let the next worker in our assembly line (the next function) the ability to determine what would be the packaging back of our item this is why the last function ismap. The last
g <- mkMatcher(pat2)will usemapthis is because its last in assembly line! so it can just do the final operation withmap( g =>which yes! pulls outgand uses thefwhich has already been pulled out from the container by theflatMaptherefore we end up with first:mkMatcher(pat) flatMap (f // pull out f function give item to next assembly line worker (you see it has access to
f, and do not package it back i mean let the map determine the packaging let the next assembly line worker determine the container. mkMatcher(pat2) map (g => f(s) ...)) // as this is the last function in the assembly line we are going to use map and pull g out of the container and to the packaging back, itsmapand this packaging will throttle all the way up and be our package or our container, yah!
mkMatcher返回一个container包含一个函数的容器:String => Boolean- 规则是如果我们有多个
<-它们转换为flatMap除了最后一个。 - 正如
f <- mkMatcher(pat)首先sequence(认为assembly line)我们想要的只是f将其传递给装配线中的下一个工人,我们让装配线中的下一个工人(下一个功能)能够确定什么是包装我们的项目这就是为什么最后一个功能是map。 最后
g <- mkMatcher(pat2)会用map这个是因为它最后在流水线上!所以它可以做最后的操作map( g =>,是的!拉出g并使用f已经从容器中拉出的 ,flatMap因此我们首先得到:mkMatcher(pat) flatMap (f // 拉出 f 函数将物品交给下一个装配线工人(你看到它可以访问
f,不要打包回去我的意思是让地图确定包装让下一个装配线工人确定container.mkMatcher(pat2) map (g => f(s) ...)) // 因为这是流水线中的最后一个函数,我们将使用 map 并将 g 从容器中拉出并返回到包装中,它map和这个包装将一路节流,成为我们的包裹或我们的容器,是的!
回答by Bruno Grieder
The rationale is to chain monadic operations which provides as a benefit, proper "fail fast" error handling.
基本原理是链接一元操作,它提供了一个好处,即正确的“快速失败”错误处理。
It is actually pretty simple. The mkMatchermethod returns an Option(which is a Monad).
The result of mkMatcher, the monadic operation, is either a Noneor a Some(x).
其实很简单。该mkMatcher方法返回一个Option(这是一个 Monad)。mkMatcher一元运算的结果是 aNone或 a Some(x)。
Applying the mapor flatMapfunction to a Nonealways returns a None- the function passed as a parameter to mapand flatMapis not evaluated.
将maporflatMap函数应用于 aNone总是返回 a None- 该函数作为参数传递给map并且flatMap不被评估。
Hence in your example, if mkMatcher(pat)returns a None, the flatMap applied to it will return a None(the second monadic operation mkMatcher(pat2)will not be executed) and the final mapwill again return a None.
In other words, if any of the operations in the for comprehension, returns a None, you have a fail fast behavior and the rest of the operations are not executed.
因此,在您的示例中,如果mkMatcher(pat)返回 None,则应用于它的 flatMap 将返回 a None(mkMatcher(pat2)不会执行第二个 monadic 操作),最终map将再次返回 a None。换句话说,如果 for comprehension 中的任何操作返回 None,则您具有快速失败行为,并且不会执行其余操作。
This is the monadic style of error handling. The imperative style uses exceptions, which are basically jumps (to a catch clause)
这是错误处理的一元风格。命令式风格使用异常,它们基本上是跳转(到 catch 子句)
A final note: the patternsfunction is a typical way of "translating" an imperative style error handling (try...catch) to a monadic style error handling using Option
最后一点:该patterns函数是将命令式错误处理(try... catch)“转换”为使用一元式错误处理的典型方式Option
回答by korefn
This can be traslated as:
这可以翻译为:
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)
Run this for a better view of how its expanded
运行它以更好地了解其扩展方式
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)
results are:
结果是:
1 -> a
1 -> b
1 -> c
...
2 -> a
2 -> b
...
This is similar to flatMap- loop through each element in patand foreach element mapit to each element in pat2
这类似于flatMap- 循环遍历中的每个元素pat并将 foreach 元素遍历map到中的每个元素pat2
回答by xiaowl
First, mkMatcherreturns a function whose signature is String => Boolean, that's a regular java procedure which just run Pattern.compile(string), as shown in the patternfunction.
Then, look at this line
首先,mkMatcher返回一个签名为 的函数String => Boolean,它是一个正常运行的 java 过程Pattern.compile(string),如pattern函数中所示。然后,看这一行
pattern(pat) map (p => (s:String) => p.matcher(s).matches)
The mapfunction is applied to the result of pattern, which is Option[Pattern], so the pin p => xxxis just the pattern you compiled. So, given a pattern p, a new function is constructed, which takes a String s, and check if smatches the pattern.
该map函数应用于 的结果pattern,即Option[Pattern],因此pinp => xxx只是您编译的模式。因此,给定一个 pattern p,将构造一个新函数,该函数接受一个 String s,并检查是否s与该模式匹配。
(s: String) => p.matcher(s).matches
Note, the pvariable is bounded to the compiled pattern. Now, it's clear that how a function with signature String => Booleanis constructed by mkMatcher.
请注意,该p变量受限于编译模式。现在,很明显带有签名的函数是如何String => Boolean由mkMatcher.
Next, let's checkout the bothMatchfunction, which is based on mkMatcher. To show how bothMathchworks, we first look at this part:
接下来,让我们检查bothMatch基于mkMatcher. 为了展示它是如何bothMathch工作的,我们首先看看这部分:
mkMatcher(pat2) map (g => f(s) && g(s))
Since we got a function with signature String => Booleanfrom mkMatcher, which is gin this context, g(s)is equivalent to Pattern.compile(pat2).macher(s).matches, which returns if the String s matches pattern pat2. So how about f(s), it's same as g(s), the only difference is that, the first call of mkMatcheruses flatMap, instead of map, Why? Because mkMatcher(pat2) map (g => ....)returns Option[Boolean], you will get a nested result Option[Option[Boolean]]if you use mapfor both call, that's not what you want .
由于我们得到了一个带有签名的函数String => Booleanfrom mkMatcher,g在这个上下文中,g(s)它等价于Pattern.compile(pat2).macher(s).matches,如果 String s 匹配 pattern 则返回pat2。那么怎么样f(s),它和 一样g(s),唯一的区别是,第一次调用mkMatcheruses flatMap,而不是map,Why?因为mkMatcher(pat2) map (g => ....)return Option[Boolean],Option[Option[Boolean]]如果您map用于这两个调用,您将获得嵌套结果,这不是您想要的。

