我了解 Ruby 和 Python 的产量。Scala 的 yield 有什么作用?
10 回答
我认为公认的答案很好,但似乎很多人未能掌握一些基本观点。
首先,Scala 的for
理解等同于 Haskell 的do
符号,它只不过是组合多个单子操作的语法糖。由于此声明很可能无法帮助任何需要帮助的人,让我们再试一次...... :-)
Scala 的for
理解是使用 mapflatMap
和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
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
,返回一个赔率列表——因为found
is false
。只有 thenforeach
被执行,但是,到这个时候,改变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
,它始终是非严格的。以下示例显示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 之间从非严格更改为严格。
它用于序列推导(如 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
有不同的效果。
是的,正如 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
。
除非您从 Scala 用户(我不是)那里得到更好的答案,否则这是我的理解。
它仅作为以 开头的表达式的一部分出现,该表达式for
说明如何从现有列表生成新列表。
就像是:
var doubled = for (n <- original) yield n * 2
所以每个输入都有一个输出项(尽管我相信有一种删除重复项的方法)。
这与 yield 在其他语言中启用的“命令式延续”完全不同,它提供了一种从几乎任何结构的命令式代码生成任意长度列表的方法。
(如果您熟悉 C#,它更接近LINQ 的 select
运算符而不是yield return
)。
yield
map
正如Daniel Sobral 已经详细解释的那样,Scala 中的关键字只是语法糖,可以很容易地用 a 代替。
另一方面,yield
如果您正在寻找类似于Python中的生成器(或延续),这绝对是一种误导。有关更多信息,请参阅此 SO 线程:在 Scala 中实现“yield”的首选方法是什么?
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 表达式中迭代的现有集合中产生一个新集合。”</p>
取自这里
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),这可能不是您想要的。