返回介绍

数学基础

统计学习

深度学习

工具

Scala

三、For 表达式

发布于 2023-07-17 23:38:23 字数 16866 浏览 0 评论 0 收藏 0

  1. 所有给出 yield 结果的 for 表达式都会被编译器翻译成对高阶函数 map,flatMapwithFilter 的调用;所有不带yield 结果的 for 表达式都被翻译成更小范围(只有 withFilterforeach )的高阶函数。

    
    
    xxxxxxxxxx
    case class Person(name:String, isMale:Boolean, children:Person*) // 找出所有母亲和其孩子的 pair 对 persons.filter(p => !p.isMale).flatMap{ case p => p.children.map( c => (p.name,c.name)) } // 或者 persons.withFilter(p => !p.isMale).flatMap.flatMap{ case p=> p.children.map(c => (p.name,c.name)) } // 或者 for (p <- persons if !p.isMale ; c<- p.children) yield (p.name,c.name)
  2. 一般而言一个 for 表达式的格式为:

    
    
    xxxxxxxxxx
    for (seq) yield expr

    seq 是一个序列的生成器generator、定义 definition 和过滤器 filter ,它们用分号隔开。

    如:

    
    
    xxxxxxxxxx
    for (p <- persons; n = p.name; if(n.startsWith("To"))) yield n

    其中 p <- persons 表示生成器,n = p.name 表示定义,if(n.startsWith("To")) 表示过滤器。

    也可以将 seq 放到花括号而不是圆括号中,此时分号就变成可选的:

    
    
    xxxxxxxxxx
    for { p <- persons // 一个生成器 n = p.name; // 一个定义 if(n.startsWith("To")) // 一个过滤器 } yield n
    • 生成器 generator 的格式为:

      
      
      xxxxxxxxxx
      pat <- expr

      这里的表达式 expr 通常返回一个列表,然后模式 pat 会跟这个列表里的每个元素依次匹配。如果匹配成功,则模式中的变量就会被绑定上该元素对应的部分;如果匹配失败,则列表的当前元素就被丢弃,并不会抛出 MatchError 异常。

      最常见的情况下,模式 pat 只是一个变量 x,如 x <- expr 。此时变量 x 仅仅是简单的遍历 expr 返回的所有元素。

    • 定义 definition 的格式为:

      
      
      xxxxxxxxxx
      pat = expr

      这个定义将模式 pat 绑定到 expr 的值,因此跟如下的 val 定义的作用是一样的:

      
      
      xxxxxxxxxx
      val x = expr

      最简单的情况是简单的变量 x

    • 过滤器filter 的格式为:

      
      
      xxxxxxxxxx
      if expr

      这里 expr 是个类型为 Boolean 的表达式。过滤器会将迭代中所有让 expr 返回 false 的元素丢弃。

  3. 每个for 表达式都以生成器开始。如果一个 for 表达式中存在多个生成器,则出现在后面的生成器比出现在前面的生成器调用得更频繁。

  4. 组合数学问题是 for 表达式特别适合的应用领域。

3.1 翻译

  1. 每个 for 表达式都可以用三个高阶函数map、flatMap、withFilter 来表示。

    • 单个生成器的 for 表达式:

      
      
      xxxxxxxxxx
      for (x <- expr1) yield expr2

      等价于:

      
      
      xxxxxxxxxx
      expr1.map(x => expr2)
    • 带一个生成器和一个过滤器的 for 表达式:

      
      
      xxxxxxxxxx
      for(x <- expr1 if expr2) yield expr3

      等价于:

      
      
      xxxxxxxxxx
      for( x <- expr1 withFilter (x => expr2)) yield expr3

      这进一步等价于:

      
      
      xxxxxxxxxx
      expr1.withFilter(x => expr2).map(x => expr3)
    • 相同的翻译机制对于过滤器后面更多元素也同样适用。如果 seq 是一组任意的生成器、定义和过滤器序列,则有:

      
      
      xxxxxxxxxx
      for (x <- expr1 if expr2; seq) yield expr3

      等价于:

      
      
      xxxxxxxxxx
      for(x <- expr1 withFilter expr2; seq) yield expr3

      然后翻译过程继续处理第二个表达式 ,这个表达式已经比原始版本少了一个元素。

    • 如果有两个生成器开始的for 表达式,则等价于对 flatMap 的应用。如:

      
      
      xxxxxxxxxx
      for(x <- expr1; y<- expr2; seq) yield expr3

      等价于:

      
      
      xxxxxxxxxx
      expr1.flatMap(x => for (y <- expr2; seq) yield expr3 )

      这里传递给 flatMap 的函数值当中,会有另一个 for 表达式。这个 for 表达式比原始版本少一个元素,因此更简单。它也会按照相同的规则翻译。

  2. 如果生成器左边的部分是模式 pat 而不是简单变量时,翻译机制变得复杂。

    • 如果 for 表达式绑定一个元组的情况相对比较容易处理,此时跟单变量的规则几乎相同。

      如:

      
      
      xxxxxxxxxx
      for ((x1,..., xn) <- expr1 ) yield expr2

      被翻译为:

      
      
      xxxxxxxxxx
      expr1.map{ case (x1,...,xn) => expr2}
    • 如果生成器左边的部分是一个任意的模式 pat 而不是单个变量或者元组时,情况更复杂。

      如:

      
      
      xxxxxxxxxx
      for (pat <- expr1) yield expr2

      被翻译为:

      
      
      xxxxxxxxxx
      expr1 withFilter { case pat => true case _ => false } map{ case pat => expr2 }

      也就是说,生成的项首先会被过滤,只有那些跟 pat 匹配的项才能进入下一步处理。因此,采用模式匹配的生成器不会抛出 MatchError

    • 这里的机制仅处理包含单个模式匹配的生成器的 for 表达式的 case。如果 for 表达式包含了其它生成器、过滤器或定义,编译器也有类似的规则来处理。

  3. for 表达式中包含内嵌定义时,如:

    
    
    xxxxxxxxxx
    for (x <- expr1; y = expr2; seq) yield expr3

    我们假设 seq 是一个(或者为空的)生成器、定义和过滤器的序列。则上述表达式被翻译为:

    
    
    xxxxxxxxxx
    for ((x,y) <- for (x <- expr1) yield (x,expr2); seq) yield epxr3

    可以看到:每当新的 x 值生成出来时,expr2 就会被重新求值。这个重新求值操作是必要的,因为 expr2 可能会用到 x ,因此需要针对 x 值的变化重新求值。

    因此可以看到:如果 y 的值在for 循环内是不变的,则没必要在for 表达式内部内嵌该定义。如:

    
    
    xxxxxxxxxx
    for (x <- expr1; y = func();) yield x + y

    其中 funcx 无关,且非常耗时。由于在 for 循环过程中 func 结果不变,且又非常耗时,因此可以修改为:

    
    
    xxxxxxxxxx
    y = func() for (x <- expr1) yield x + y
  4. 如果 for 循环只是简单的执行副作用,但是并不返回任何值(没有 yield 表达式),则其翻译也是类似的。

    从原理上讲,之前的翻译机制中用到 mapflatMap 的地方,这里都用 foreach

    如:

    
    
    xxxxxxxxxx
    for ( x <- expr1) body

    等价于:

    
    
    xxxxxxxxxx
    expr1 foreach (x => body)

    如:

    
    
    xxxxxxxxxx
    for (x <- expr1; if expr2; y <- expr3) body

    等价于:

    
    
    xxxxxxxxxx
    expr1 withFilter (x => expr2) foreach ( x=> expr3 foreach (y => body ) )
  5. 事实上,也可以把高阶函数翻译成 for 表达式:每个 mapflatMapfilter 的应用也可以由 for 表达式来表示。

    
    
    xxxxxxxxxx
    object Demo{ def map[A, B](xs: List[A], f: A=> B) : List[B] = for (x <- xs) yield f(x) def flatMap[A, B](xs: List[A], f: A => List[B]) : List[B] = for (x <- xs; y <- f(x)) yield y def filter[A](xs: List[A], p: A => Boolean): List[A] = for ( x <- xs if p(x)) yield x }

    因此不难看出:for 表达式就是对 map,flatMap,withFilter 这三个函数的等效表达。

3.2 泛化 for

  1. 由于编译器对 for 表达式的翻译仅依赖于相应的 map,flatMap,withFilter 方法,因此我们可以对很多数据类型应用 for 表达式。

    • 除了列表、数组之外,Scala 标准类库中还有很多类型支持 map,flatMap,withFilter,foreach 方法,因此允许对它们使用 for 表达式。

    • 对于自定义数据类型,你也可以通过支持这四个方法来支持 for 表达式。你也可以仅支持其中的一部分方法,从而部分的支持 for 表达式。

      • 如果你的类型仅定义了 map 方法,则你可以对该类型的对象应用包含单个生成器的 for 表达式。
      • 如果你的类型同时定义了 map, flatMap 方法,则你可以对该类型的对象应用包含单个或者多个生成器的 for 表达式。
      • 如果你的类型仅定义了 foreach 方法,则你可以对该类型的对象应用for 循环(而不是表达式),此时可以支持单个生成器或者多个生成器。
      • 如果你的类型定义了 withFilter 方法,则你可以对该类型的对象应用的for 循环或者 for 表达式中存在 if 过滤器。
  2. for 表达式的翻译发生在类型检查之前。Scala 对于 for 表达式本身没有规定任何类型规则,也不要求 map,flatMap,withFilter,foreach 有任何特定的类型签名。

    如:

    
    
    xxxxxxxxxx
    abstract class C[A] { def map[B](f: A => B): C[B] def flatMap[B](f: A => C[B]): C[B] def withFilter(p: A => Boolean): C[A] def foreach(b: A=> Unit): Unit }

    在这里 withFilter 产出相同类的新集合,这意味着每次 withFilter 调用都会创建新的 C 对象,就跟 filter 做的事情一样。

    如果 withFilter 创建的对象会被接下来某个方法再次解开,且 C 对象很大(比如很长的一个字符串),则你可能希望避免创建这个中间对象。一个标准的做法是:让 withFilter 不要返回 C 的对象,而是返回一个“记住”这个元素需要被过滤的包装器,然后继续处理。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文