ChatGPT解决这个技术问题 Extra ChatGPT

Scala 的产量是多少?

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


D
Daniel C. Sobral

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

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

Scala 的 for 理解是使用 map、flatMapfilter 组合多个操作的语法糖。或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 一起应用,返回一个赔率列表——因为 foundfalse。只有这样 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 之间从非严格更改为严格。


scala 2.8 中有一个新方法 withFilter 。 for(x <- c; if cond) yield {...} 在 scala2.8 中转换为 c.withFilter(x => cond).map(x => {...})。
@Eastsun 确实如此,尽管也有自动回退。 withFilter 也应该是非严格的,即使对于严格的集合也是如此,这值得一些解释。我会考虑这个...
@Daniel:Odersky 等人在“Scala 编程”中对这个主题进行了很好的处理。 (我相信你已经知道了)。 +1 显示它。
前 2 点是正确的: 1. 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...}) 还是我遗漏了什么?
D
Dario

它在 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 有不同的效果。


那么为什么我要使用yield而不是map呢?此地图代码等效于 val res = args.map(_.toUpperCase) ,对吧?
如果您更喜欢语法。此外,正如 alexey 所指出的,推导式还为访问 flatMap、filter 和 foreach 提供了很好的语法。
正确的。如果你只有一个简单的地图——一个没有 if 的生成器——我肯定会说调用 map 更具可读性。如果您有多个相互依赖的生成器和/或过滤器,您可能更喜欢 for 表达式。
请注意,给出的示例不等同于 map 表达式:它是相同的。 for comprehension 被翻译为对 map、flatMap 和 filter 的调用。
答案是这样开始的:“它用于序列推导(如 Python 的列表推导和生成器,您也可以在其中使用 yield)。”这错误地导致人们认为 Scala 中的 yield 类似于 Python 中的 yield。不是这种情况。在 Python 中,yield 用于协程(或延续)的上下文中,而在 Scala 中并非如此。如需更多说明,请访问此主题:stackoverflow.com/questions/2201882/…
f
fredoverflow

是的,正如 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 表达式。


@Eldritch Conundrum - 有趣的是,原始 SQL 规范概述的顺序相同。 SQL 语言在某种程度上颠倒了顺序,但是首先描述你从中提取什么,然后描述你期望从中得到什么是完全有意义的。
E
Erik Kaplun

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

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

就像是:

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

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

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

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


C
Community

考虑以下 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


C
Community

Scala 中的关键字 yield 只是语法糖,可以很容易地用 map 替换,具体为 Daniel Sobral already explained

另一方面,如果您正在寻找类似于 those in Python 的生成器(或延续),则 yield 绝对会产生误导。有关详细信息,请参阅此 SO 线程:What is the preferred way to implement 'yield' in Scala?


M
Manasa Chada

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))

希望这可以帮助!!


在回答这么老的问题(超过 9 年前)时,指出您的答案与已提交的所有其他答案有何不同会很有帮助。
我认为澄清疑问很重要,不要给出不同的答案,因为即使我也是学习这门语言的初学者。谢谢你的建议。
d
dotnetN00b
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 一样灵活,反之亦然。


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

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

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

取自 here


M
Michael Peng

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


这种比较是错误的。你在比较两个不同的东西。 yield 中的表达式与 map 中的表达式完全不同。此外,与地图相比,它根本没有显示出产量的“灵活性”。