sc_*_*ray 82 monads scala map for-comprehension
我似乎真的不了解Map和FlatMap.我无法理解的是for-comprehension是如何嵌套调用map和flatMap的.以下示例来自Scala中的Functional Programming
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
f <- mkMatcher(pat)
g <- mkMatcher(pat2)
} yield f(s) && g(s)
Run Code Online (Sandbox Code Playgroud)
翻译成
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] =
mkMatcher(pat) flatMap (f =>
mkMatcher(pat2) map (g => f(s) && g(s)))
Run Code Online (Sandbox Code Playgroud)
mkMatcher方法定义如下:
def mkMatcher(pat:String):Option[String => Boolean] =
pattern(pat) map (p => (s:String) => p.matcher(s).matches)
Run Code Online (Sandbox Code Playgroud)
模式方法如下:
import java.util.regex._
def pattern(s:String):Option[Pattern] =
try {
Some(Pattern.compile(s))
}catch{
case e: PatternSyntaxException => None
}
Run Code Online (Sandbox Code Playgroud)
如果有人能够阐明在这里使用map和flatMap背后的理由,那将会很棒.
pag*_*_5b 188
TL; DR直接进入最后的例子
我会试着回顾一下
定义
的for理解是一个语法快捷方式相结合flatMap,并map以一种易于阅读和推理.
让我们稍微简化一下,并假设每个class提供上述两种方法的都可以被称为a monad,我们将使用该符号M[A]来表示monad具有内部类型的a A.
例子
一些常见的单子
List[String] 哪里
M[X] = List[X] A = StringOption[Int] 哪里
M[X] = Option[X]A = IntFuture[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]
Run Code Online (Sandbox Code Playgroud)
例如
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)
Run Code Online (Sandbox Code Playgroud)
表达
使用<-符号的表达式中的每一行都被转换为一个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
Run Code Online (Sandbox Code Playgroud)只有一个的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
Run Code Online (Sandbox Code Playgroud)现在到了这一点
正如您所看到的,该map操作保留了原始的"形状" monad,因此对于yield表达式也会发生同样的情况:a List仍然是List由内容中的操作转换的内容yield.
另一方面,每个绑定线for只是连续的组合monads,必须"展平"以保持单一的"外部形状".
假设片刻将每个内部绑定转换为一个map调用,但右手是相同的A => M[B]函数,您最终会M[M[B]]在理解中为每一行输出一个.
整个for语法的目的是轻松地"平坦化"连续monadic操作的连接(即"提升""monadic形状"中的值的操作:) A => M[B],并添加可能执行结束转换的最终map操作.
我希望这解释了翻译选择背后的逻辑,它以机械的方式应用,即:n flatMap通过单个调用结束的嵌套调用map.
一个人为的说明性示例
意味着显示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 (_ + _)
}
Run Code Online (Sandbox Code Playgroud)
你能猜出它的类型valuesList吗?
前面已经说了的形状monad是通过理解维持,所以我们以a开头List的company.branches,并且必须以结束List.
而内部类型改变并由yield表达式确定:即customer.value: Int
valueList 应该是一个 List[Int]
我不是斯卡拉超级头脑,所以随时可以纠正我,但这就是我flatMap/map/for-comprehension向自己解释传奇的方式!
要理解for comprehension它及其译文,scala's map / flatMap我们必须采取一些小步骤,并理解组成部分- map和flatMap。但是,scala's flatMap不仅仅是map与flatten您问自己!如果是的话,为什么那么多的开发者觉得很难掌握它或理解for-comprehension / flatMap / map。好吧,如果仅查看scala map和flatMap签名,您会看到它们返回相同的返回类型,M[B]并且它们在相同的输入参数上工作A(至少是所采用函数的第一部分),如果这样做有什么不同呢?
我们的计划
map。flatMap。for comprehension。斯卡拉的地图
Scala地图签名:
map[B](f: (A) => B): M[B]
Run Code Online (Sandbox Code Playgroud)
但是,当我们查看此签名时,有很大一部分缺失,它是-这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]
Run Code Online (Sandbox Code Playgroud)
请注意有关map的一个极其重要的事实 -它会自动捆绑在M[B]您无法控制的输出容器中。让我们再次强调一下:
map为我们选择输出容器,它将与我们处理的源M[A]容器相同,因此对于容器,我们M仅获得相同的容器,B 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]
Run Code Online (Sandbox Code Playgroud)
您会看到map与flatMapflatMap 的巨大区别,我们为它提供的功能不仅是将其转换为,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)
Run Code Online (Sandbox Code Playgroud)
我们在这里得到了什么:
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和这个包装将一路加速前进,成为我们的包装或我们的容器,是的!
其基本原理是链接 monadic 操作,它提供了一个好处,即正确的“快速失败”错误处理。
其实很简单。该mkMatcher方法返回一个Option(这是一个 Monad)。mkMatcher一元运算的结果是 aNone或 a Some(x)。
将maporflatMap函数应用于 aNone总是返回 a None- 该函数作为参数传递给map并且flatMap不被评估。
因此,在您的示例中,如果mkMatcher(pat)返回 None,则应用于它的 flatMap 将返回 a None(mkMatcher(pat2)不会执行第二个 monadic 操作),最终map将再次返回 a None。换句话说,如果 for comprehension 中的任何操作返回 None,则您具有快速失败行为,并且不会执行其余操作。
这是错误处理的一元风格。命令式风格使用异常,它们基本上是跳转(到 catch 子句)
最后一点:该patterns函数是将命令式错误处理(try... catch)“转换”为使用一元式错误处理的典型方式Option