当前位置: 首页 > 知识库问答 >
问题:

与平面图/Map转换的理解混淆

厍光霁
2023-03-14

我似乎真的不了解Map和FlatMap。我无法理解的是,for-comprehensiion是如何对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)

转化为

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背后的基本原理,那就太好了。

共有3个答案

宋明亮
2023-03-14

其基本原理是链接 monadic 操作,这提供了一个好处,即适当的“快速失败”错误处理。

其实很简单。mkMatcher方法返回一个选项(这是一个单变量)。mkMatcher的一元运算结果要么是None,要么是Some(x)

map平面地图函数应用于总是返回-作为参数传递给map平面地图的函数不计算。

因此,在您的示例中,如果 mkMatcher(pat) 返回 None,则应用于它的 flatMap 将返回 None(不会执行第二个一元运算 mkMatcher(pat2),并且最终映射将再次返回 None。换句话说,如果 for understanding 中的任何操作返回 None,则表示您有一个快速失败行为,其余操作未执行。

这是一元错误处理风格。命令式样式使用异常,基本上是跳转(到catch子句)

最后注意:模式函数是使用Option将命令式样式错误处理(try...catch)“转换”为一元样式错误处理的典型方式

杨高翰
2023-03-14

我不是scala超级头脑,所以请随意纠正我,但这是我对自己解释< code > flat map/map/for-comprehension 传奇的方式!

为了理解<code>的理解</code>及其到<code>scala地图/平面地图的转换</code>我们必须采取一些小步骤,理解组成部分-<code>地图</code>和<code>平面地图</code>。但是,你问自己,scala的平面图不是<code>吗?<code>只是<code>地图</code>中的<code<展平</code>而已!如果是这样的话,为什么那么多开发人员发现很难掌握它或<code>来理解/flatMap/map。如果你只看scala的<code>映射</code>和<code>平面映射</code>签名,你会发现它们返回相同的返回类型<code>M[B],它们使用相同的输入参数<code>A</code>(至少是它们所用函数的第一部分),如果是这样,有什么区别?

我们的计划

    < li >了解scala的< code>map。 < li >了解scala的< code>flatMap。 < li >理解scala的< code > for comprehensive 。`

斯卡拉的地图

scala map signature:

map[B](f: (A) => B): M[B]

但是,当我们看这个签名时,有一个很大的部分丢失了,那就是——这个< code>A来自哪里?我们的容器属于< code>A类型,因此在容器的上下文中查看该函数非常重要。我们的容器可以是类型为< code>A的项目的< code >列表,我们的< code>map函数接受一个函数,该函数将类型为< code>A的每个项目转换为类型为< code>B的项目,然后返回类型为< code>B(或< code>M[B])的容器

让我们在考虑容器的情况下编写 map 的签名:

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]

请注意一个关于map的非常非常重要的事实——它会自动捆绑在输出容器M[B]中,您无法控制它。让我们再次强调它:

    < li> map为我们选择输出容器,它将与我们处理的源容器是同一个容器,因此对于< code>M[A]容器,我们只为< code>B M[B]获得同一个< code>M容器,其他什么也没有! < li> map为我们实现了这种容器化,我们只需给出从< code>A到< code>B的映射,它会将它放入< code>M[B]的框中,它会将它放入框中!

您会看到您没有指定如何容器化刚刚指定的项如何转换内部项。由于我们对M[A]M[B]都有相同的容器M,这意味着M[B]是相同的容器,这意味着如果你有List[A],那么你将有一个List[B],更重要的是map是为你做的!

既然我们已经处理了地图,让我们继续讨论平面地图

Scala的平面图

让我们看看它的签名:

flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]

你可以看到在flatMap中从map到flatMap的巨大差异,我们为其提供了一个功能,该功能不仅可以从A转换为B,还可以将其容器化为M[B]

为什么我们关心谁做集装箱运输?

那么,为什么我们如此关注map/flatMap的输入函数,是将容器化为< code>M[B]还是map本身为我们进行html" target="_blank">容器化呢?

您可以在理解的上下文中看到正在发生的事情是for中提供的项目的多次转换,因此我们赋予装配线中的下一个工人确定包装的能力。想象一下,我们有一条装配线,每个工人都对产品做了一些事情,只有最后一个工人将其包装在容器中!欢迎来到平面图这是它的目的,在map中,每个工人在完成对项目的工作时也将其打包,以便您在容器上获得容器。

理解力最强的人

现在让我们来看看你的理解,考虑到我们上面所说的:

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
    f <- mkMatcher(pat)   
    g <- mkMatcher(pat2)
} yield f(s) && g(s)

我们得到了什么:

>

  • mkMatcher返回一个容器。该容器包含一个函数:字符串=

    mkMatcher(pat)flat map(f//pull out f function)将物品交给下一个装配线工人(您可以看到它可以访问< code>f,并且不要将其打包回去我的意思是让地图确定包装让下一个装配线工人确定容器。mkMatcher(pat2)映射(g =

  • 白子昂
    2023-03-14

    太长别读直接到最后一个例子

    我试着复述一下。

    定义

    用于理解的是一种语法快捷方式,可以以易于阅读和推理的方式组合flatMap

    让我们稍微简化一下,假设提供上述两种方法的每个都可以称为monad,我们将使用符号M[a]来表示具有内部类型amonad

    举例

    一些常见的monads包括:

    • 列出[String]所在
      • M[X]=列表[X]
      • A=字符串
      • M[X]=选项[X]
      • A=Int
      • M[X] = 未来[X]
      • 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]
      

      例如。

        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)
      

      为了表达

      > < li>

      表达式中的每一行使用< code >

      // 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表达式

      // 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
      

      现在说到点子上

      如您所见,映射操作保留了原始单子的“形状”,因此产量表达式也是如此:一个列表仍然是一个列表</code>,其内容由收益中的操作转换。

      另一方面,for中的每条绑定线只是连续monads的组合,必须将其“扁平化”以保持单个“外部形状”。

      假设每个内部绑定都被转换成一个< code>map调用,但是右边的是同一个< code>A =

      我希望这能解释翻译选择背后的逻辑,它是以机械的方式应用的,即:nflatMap嵌套调用由单个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 (_ + _)
      }
      

      你能猜出< code>valuesList的类型吗?

      如前所述,< code>monad的形状是通过理解来保持的,因此我们从< code>company.branches中的< code>List开始,并且必须以< code>List结束。< br >内部类型会发生变化,并由< code>yield表达式确定:即< code>customer.value: Int

      value eList应该是一个List[Int]

     类似资料: