我了解 Ruby 和 Python 的产量。 Scala 的 yield 有什么作用?
我认为公认的答案很好,但似乎很多人未能掌握一些基本观点。
首先,Scala 的 for
推导等同于 Haskell 的 do
表示法,它只不过是组合多个单子操作的语法糖。由于此声明很可能无法帮助任何需要帮助的人,让我们再试一次...... :-)
Scala 的 for
理解是使用 map、flatMap
和 filter
组合多个操作的语法糖。或foreach
。 Scala 实际上将 for
表达式转换为对这些方法的调用,因此任何提供它们的类或它们的子集都可以与 for 理解一起使用。
首先,让我们谈谈翻译。有非常简单的规则:
这个 for(x <- c1; y <- c2; z <-c3) {...} 被翻译成 c1.foreach(x => c2.foreach(y => c3.foreach(z => {.. .}))) 这个 for(x <- c1; y <- c2; z <- c3) yield {...} 被翻译成 c1.flatMap(x => c2.flatMap(y => c3.map( z => {...}))) 这个 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 可用,则回退到前者。有关这方面的更多信息,请参阅以下部分。这个 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
带过滤器
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
立即与 List
一起应用,返回一个赔率列表——因为 found
是 false
。只有这样 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 引入了 withFilter
,无论集合的严格程度如何,它总是是非严格的。以下示例显示了在 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
这会产生大多数人期望的结果,而不会改变 filter
的行为方式。附带说明一下,Range
在 Scala 2.7 和 Scala 2.8 之间从非严格更改为严格。
它在 sequence comprehensions 中使用(如 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
在林克。
Ruby 的 yield
有不同的效果。
是的,正如 Earwicker 所说,它几乎等同于 LINQ 的 select
,与 Ruby 和 Python 的 yield
几乎没有关系。基本上,您将在 C# 中的哪个位置编写
from ... select ???
在 Scala 中,你有
for ... yield ???
同样重要的是要了解 for
理解不仅适用于序列,而且适用于定义某些方法的任何类型,就像 LINQ:
如果您的类型只定义了 map,它允许由单个生成器组成的 for 表达式。
如果它定义了 flatMap 和 map,它允许由多个生成器组成的 for 表达式。
如果它定义了 foreach,它允许没有 yield 的 for 循环(包括单个和多个生成器)。
如果它定义了过滤器,则它允许在 for 表达式中以 if 开头的 for-filter 表达式。
除非您从 Scala 用户(我不是)那里得到更好的答案,否则这是我的理解。
它仅作为以 for
开头的表达式的一部分出现,该表达式说明如何从现有列表生成新列表。
就像是:
var doubled = for (n <- original) yield n * 2
所以每个输入都有一个输出项(尽管我相信有一种删除重复项的方法)。
这与 yield 在其他语言中启用的“命令式延续”完全不同,它提供了一种从几乎任何结构的命令式代码生成任意长度列表的方法。
(如果您熟悉 C#,它更接近 LINQ's select
运算符而不是 yield return
)。
考虑以下 for-comprehension
val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i
如下大声朗读可能会有所帮助
"对于每个整数 i
,如果它大于 3
,则 yield(产生)i
和将其添加到列表 A
。"
就数学set-builder notation而言,上述理解类似于
https://latex.codecogs.com/gif.latex?A%20%3D%20%5Cleft%20%5C%7B%20i%20%5Cin%20%5Cmathbb%7BZ%7D%20%3A%20i%3E3%20%5Cright%20%5C%7D
可以读作
https://latex.codecogs.com/gif.latex?i
或者作为
https://latex.codecogs.com/gif.latex?A
Scala 中的关键字 yield
只是语法糖,可以很容易地用 map
替换,具体为 Daniel Sobral already explained。
另一方面,如果您正在寻找类似于 those in Python 的生成器(或延续),则 yield
绝对会产生误导。有关详细信息,请参阅此 SO 线程:What is the preferred way to implement 'yield' in Scala?
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))
希望这可以帮助!!
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 doubledNums = for (n <- nums) yield n * 2
val ucNames = for (name <- names) yield name.capitalize
请注意,这两个 for 表达式都使用了 yield 关键字:
在 for 之后使用 yield 是“秘诀”,它说:“我想使用所示算法从我在 for 表达式中迭代的现有集合中产生一个新集合。”
取自 here
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),这可能不是您想要的。
withFilter
也应该是非严格的,即使对于严格的集合也是如此,这值得一些解释。我会考虑这个...for(x <- c; y <- x; z <-y) {...}
被翻译成c.foreach(x => x.foreach(y => y.foreach(z => {...})))
2.for(x <- c; y <- x; z <- y) yield {...}
被翻译成c.flatMap(x => x.flatMap(y => y.map(z => {...})))
for(x <- c; y = ...) yield {...}
真的翻译成c.map(x => (x, ...)).map((x,y) => {...})
吗?我认为它被翻译成c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})
还是我遗漏了什么?