我理解Ruby和Python的优点。Scala的yield是做什么的?


它用于序列推导式(就像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 ]

or

from a in args select a.toUpperCase 

在Linq中。

Ruby的产量有不同的影响。


除非你能从Scala用户那里得到更好的答案(我不是),以下是我的理解。

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

喜欢的东西:

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

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

这与yield在其他语言中所支持的“命令式延续”有很大不同,在其他语言中,它提供了一种方法,可以从一些具有几乎任何结构的命令式代码生成任意长度的列表。

(如果你熟悉c#,它更接近LINQ的选择操作符,而不是yield return)。


是的,正如Earwicker所说,它几乎等同于LINQ的select,与Ruby和Python的yield关系不大。基本上,在c#中你会写

from ... select ??? 

而在Scala中则相反

for ... yield ???

同样重要的是要理解for推导式不仅适用于序列,还适用于任何定义了特定方法的类型,就像LINQ:

如果你的类型只定义map,它允许由 单独的发电机。 如果它定义了flatMap和map,则允许for-expression组成 几个发电机。 如果定义了foreach,则允许for循环而不产生yield(单个和多个生成器都可以)。 如果它定义了filter,则允许以If开头的for-filter表达式 在for表达式中。


我认为公认的答案很好,但似乎许多人没有抓住一些基本的问题。

首先,Scala的for推导式等同于Haskell的do表示法,它只不过是组合多个一元操作的语法糖。因为这句话很可能对任何需要帮助的人都没有帮助,让我们再试一次…:-)

Scala的推导式是用map、flatMap和filter组合多个操作的语法糖。或foreach。Scala实际上将for表达式转换为对这些方法的调用,因此任何提供它们的类或它们的子集都可以用于推导式。

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

This for(x <- c1; y <- c2; z <-c3) {...} is translated into c1.foreach(x => c2.foreach(y => c3.foreach(z => {...}))) This for(x <- c1; y <- c2; z <- c3) yield {...} is translated into c1.flatMap(x => c2.flatMap(y => c3.map(z => {...}))) This for(x <- c; if cond) yield {...} is translated on Scala 2.7 into c.filter(x => cond).map(x => {...}) or, on Scala 2.8, into c.withFilter(x => cond).map(x => {...}) with a fallback into the former if method withFilter is not available but filter is. Please see the section below for more information on this. This for(x <- c; y = ...) yield {...} is translated into c.map(x => (x, ...)).map((x,y) => {...})

当你看非常简单的理解,地图/foreach替代方案看起来确实更好。一旦你开始组合它们,你就很容易迷失在括号和嵌套级别中。当这种情况发生时,理解通常要清楚得多。

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

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

or

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

withFilter

Scala 2.8引入了一个名为withFilter的方法,其主要区别在于,它不是返回一个新的、经过过滤的集合,而是按需过滤。过滤器方法的行为是根据集合的严格程度定义的。为了更好地理解这一点,让我们来看看一些带有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立即与List一起应用,返回一个赔率列表——因为found为假。然后才执行foreach,但是,此时更改found是没有意义的,因为filter已经执行了。

在Stream的情况下,条件不会立即应用。相反,当foreach请求每个元素时,filter测试条件,这使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引入了filter,无论集合的严格程度如何,它总是不严格的。下面的例子展示了在Scala 2.8上使用这两种方法的List:

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

这产生了大多数人期望的结果,而不改变过滤器的行为。顺便说一句,在Scala 2.7和Scala 2.8之间,Range从非严格变成了严格。


关键字yield在Scala中只是简单的语法糖,可以很容易地用映射替换,Daniel Sobral已经详细解释过了。

另一方面,如果你在寻找类似于Python中的生成器(或延续),yield绝对会误导你。查看这个SO线程以获得更多信息:在Scala中实现“yield”的首选方式是什么?


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),这可能不是你想要的。


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一样灵活,反之亦然。


为了理解,考虑以下内容

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

大声读出来可能会有帮助

对于每一个整数i,如果它大于3,那么yield (produce) i并将其添加到列表a中。

在数学集合构建符号方面,上面的for-comprehension类似于

这可以理解为

"对于每一个整数,如果它大于,那么它就是集合中的成员"

或者作为替代

"是所有整数的集合,每个都大于"


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)] =列表((1),(b)、(1 c), (2), (2 b), (2 c), (3), (b)、(3 c))

希望这能有所帮助!!


val doubledNums = for (n <- nums) yield n * 2
val ucNames = for (name <- names) yield name.capitalize

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

在for之后使用yield是“秘密武器”,它表示“我想从我在for表达式中迭代的现有集合中生成一个新的集合,使用所示的算法。”

从这里开始


根据Scala文档,它清楚地说“从现有的集合中生成一个新的集合”。

另一个Scala文档说,“Scala提供了一种轻量级符号来表示序列推导。推导式的形式为(enums) yield e,其中enums指以分号分隔的枚举数列表。枚举器可以是引入新变量的生成器,也可以是过滤器。”