331

我了解 Ruby 和 Python 的产量。Scala 的 yield 有什么作用?

4

10 回答 10

855

我认为公认的答案很好,但似乎很多人未能掌握一些基本观点。

首先,Scala 的for理解等同于 Haskell 的do符号,它只不过是组合多个单子操作的语法糖。由于此声明很可能无法帮助任何需要帮助的人,让我们再试一次...... :-)

Scala 的for理解是使用 mapflatMapfilter. 或foreach。Scala 实际上将for- 表达式转换为对这些方法的调用,因此任何提供它们的类或它们的子集都可以与 for 理解一起使用。

首先,让我们谈谈翻译。有非常简单的规则:

  1. for(x <- c1; y <- c2; z <-c3) {...}
    

    被翻译成

    c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
    
  2. for(x <- c1; y <- c2; z <- c3) yield {...}
    

    被翻译成

    c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
    
  3. for(x <- c; if cond) yield {...}
    

    在 Scala 2.7 上被翻译成

    c.filter(x => cond).map(x => {...})
    

    或者,在 Scala 2.8 上,进入

    c.withFilter(x => cond).map(x => {...})
    

    withFilter如果方法不可用但可用,则回退到前者filter。有关这方面的更多信息,请参阅以下部分。

  4. for(x <- c; y = ...) yield {...}
    

    被翻译成

    c.map(x => (x, ...)).map((x,y) => {...})
    

当您查看非常简单for的推导时,map/foreach替代方案看起来确实更好。但是,一旦开始编写它们,您很容易迷失在括号和嵌套级别中。当发生这种情况时,for理解通常会更加清晰。

我将展示一个简单的示例,并有意省略任何解释。您可以决定哪种语法更容易理解。

l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))

或者

for {
  sl <- l
  el <- sl
  if el > 0
} yield el.toString.length

withFilter

Scala 2.8 引入了一个名为 的方法withFilter,其主要区别在于,它不是返回一个新的过滤集合,而是按需过滤。该filter方法的行为基于集合的严格性定义。为了更好地理解这一点,让我们看一下带有List(严格)和Stream(非严格)的一些 Scala 2.7:

scala> var found = false
found: Boolean = false

scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9

scala> found = false
found: Boolean = false

scala> Stream.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3

之所以会出现差异,是因为filter立即应用 with List,返回一个赔率列表——因为foundis false。只有 thenforeach被执行,但是,到这个时候,改变found已经没有意义了,因为filter已经执行了。

在 的情况下Stream,不会立即应用条件。相反,当每个元素都由 请求时foreachfilter测试条件,这可以通过foreach来影响它found。为了清楚起见,这里是等效的理解代码:

for (x <- List.range(1, 10); if x % 2 == 1 && !found) 
  if (x == 5) found = true else println(x)

for (x <- Stream.range(1, 10); if x % 2 == 1 && !found) 
  if (x == 5) found = true else println(x)

这引起了很多问题,因为人们希望if按需考虑,而不是事先将其应用于整个集合。

Scala 2.8 引入了,无论集合的严格程度如何withFilter,它始终是非严格的。以下示例显示List了 Scala 2.8 上的两种方法:

scala> var found = false
found: Boolean = false

scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9

scala> found = false
found: Boolean = false

scala> List.range(1,10).withFilter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3

这会产生大多数人期望的结果,而不会改变filter行为方式。作为旁注,Range在 Scala 2.7 和 Scala 2.8 之间从非严格更改为严格。

于 2009-06-29T17:33:01.893 回答
211

它用于序列推导(如 Python 的列表推导和生成器,您也可以在其中使用yield)。

它与新元素结合应用for并将新元素写入结果序列。

简单示例(来自scala-lang

/** Turn command line arguments to uppercase */
object Main {
  def main(args: Array[String]) {
    val res = for (a <- args) yield a.toUpperCase
    println("Arguments: " + res.toString)
  }
}

F# 中的相应表达式为

[ for a in args -> a.toUpperCase ]

或者

from a in args select a.toUpperCase 

在林克。

Rubyyield有不同的效果。

于 2009-06-27T09:42:19.530 回答
24

是的,正如 Earwicker 所说,它几乎等同于 LINQ select,与 Ruby 和 Python 的yield. 基本上,您将在 C# 中的哪个位置编写

from ... select ??? 

在 Scala 中,你有

for ... yield ???

同样重要的是要理解for-comprehensions 不仅适用于序列,而且适用于定义某些方法的任何类型,就像 LINQ:

  • 如果您的类型定义了 just map,它允许for-expressions 由单个生成器组成。
  • 如果它定义flatMap了 以及map,它允许for由多个生成器组成的 - 表达式。
  • 如果它定义了foreach,它允许for-loops 没有 yield (包括单个和多个生成器)。
  • 如果它定义了filter,则它允许for-filter 表达式以表达式if 中的an 开头for
于 2009-06-27T12:31:35.803 回答
15

除非您从 Scala 用户(我不是)那里得到更好的答案,否则这是我的理解。

它仅作为以 开头的表达式的一部分出现,该表达式for说明如何从现有列表生成新列表。

就像是:

var doubled = for (n <- original) yield n * 2

所以每个输入都有一个输出项(尽管我相信有一种删除重复项的方法)。

这与 yield 在其他语言中启用的“命令式延续”完全不同,它提供了一种从几乎任何结构的命令式代码生成任意长度列表的方法。

(如果您熟悉 C#,它更接近LINQ 的 select运算符而不是yield return)。

于 2009-06-27T09:52:58.917 回答
13

考虑以下理解

val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i

如下大声朗读可能会有所帮助

对于每个整数i如果大于3,则产生(产生)i并将其添加到列表中A。”

就数学集合构建符号而言,上述理解类似于

集合记法

可以读作

对于每个整数一世如果大于3,则它是集合的成员一种。”

或者作为

"一种是所有整数的集合,每个整数一世一世大于3。"

于 2015-12-23T22:23:44.043 回答
12

yieldmap正如Daniel Sobral 已经详细解释的那样,Scala 中的关键字只是语法糖,可以很容易地用 a 代替。

另一方面,yield如果您正在寻找类似于Python中的生成器(或延续),这绝对是一种误导。有关更多信息,请参阅此 SO 线程:在 Scala 中实现“yield”的首选方法是什么?

于 2012-07-07T12:27:09.617 回答
5

Yield 类似于 for 循环,它有一个我们看不到的缓冲区,并且对于每个增量,它都会不断地将下一项添加到缓冲区中。当 for 循环完成运行时,它将返回所有产生值的集合。Yield 可以用作简单的算术运算符,甚至可以与数组结合使用。这里有两个简单的例子,让你更好地理解

scala>for (i <- 1 to 5) yield i * 3

res: scala.collection.immutable.IndexedSeq[Int] = Vector(3, 6, 9, 12, 15)

scala> val nums = Seq(1,2,3)
nums: Seq[Int] = List(1, 2, 3)

scala> val letters = Seq('a', 'b', 'c')
letters: Seq[Char] = List(a, b, c)

scala> val res = for {
     |     n <- nums
     |     c <- letters
     | } yield (n, c)

res: Seq[(Int, Char)] = List((1,a), (1,b), (1,c), (2,a), (2,b), (2,c), ( 3,a), (3,b), (3,c))

希望这可以帮助!!

于 2018-08-12T12:02:32.140 回答
0
val aList = List( 1,2,3,4,5 )

val res3 = for ( al <- aList if al > 3 ) yield al + 1
val res4 = aList.filter(_ > 3).map(_ + 1)

println( res3 )
println( res4 )

这两段代码是等价的。

val res3 = for (al <- aList) yield al + 1 > 3
val res4 = aList.map( _+ 1 > 3 )

println( res3 ) 
println( res4 )

这两段代码也是等价的。

Map 与 yield 一样灵活,反之亦然。

于 2013-10-25T23:59:31.533 回答
0
val doubledNums = for (n <- nums) yield n * 2
val ucNames = for (name <- names) yield name.capitalize

请注意,这两个 for 表达式都使用了yield关键字:

在for之后使用yield是“秘诀”,它说:“我想使用所示算法从我在 for 表达式中迭代的现有集合中产生一个新集合。”</p>

取自这里

于 2021-08-20T10:40:53.097 回答
-3

yield 比 map() 更灵活,见下面的例子

val aList = List( 1,2,3,4,5 )

val res3 = for ( al <- aList if al > 3 ) yield al + 1 
val res4 = aList.map( _+ 1 > 3 ) 

println( res3 )
println( res4 )

yield 将打印结果如:List(5, 6),这很好

而 map() 将返回如下结果:List(false, false, true, true, true),这可能不是您想要的。

于 2013-10-10T18:24:46.153 回答