在Scala中,for表达式与foreach相比有何区别?

54

我正在学习Scala编程语言,虽然我很想从Python的角度去看问题,但我不想用Scala编写"Python in Scala"。

就控制流而言,我不太确定该怎么做:在Python中,我们经常使用for x in some_iterable,并且非常喜欢它。在Scala中,存在一种非常类似的结构,Odersky将其称为for表达式,可能是为了区别于Java for循环。此外,在Scala中,可迭代数据类型有一个名为foreach的属性(我猜它应该算是属性,不过我对Scala的了解不够,无法准确命名它),但看起来我不能用foreach来做更多的事情,除非是针对容器中的每个项目调用一个函数。

这让我有几个问题。首先,像在Python中那样,for表达式在Scala中是否是重要的/广泛使用的结构?其次,在什么情况下应该使用foreach而不是for表达式(除了显而易见的针对容器中的每个项目调用函数的情况)?

希望我没有过于模糊或过于笼统,但我只是试图理解Scala中的一些设计/语言基础知识(目前看起来非常酷)。

5个回答

60

Python 在列表推导和生成器表达式中使用 for。这些与 Scala 中的 for 表达式非常相似:

这是 Python。

>>> letters = ['a', 'b', 'c', 'd']
>>> ints = [0, 1, 2, 3]
>>> [l + str(i) for l in letters for i in ints if i % 2 == 0]
['a0', 'a2', 'b0', 'b2', 'c0', 'c2', 'd0', 'd2']

这是Scala

scala> val letters = List('a', 'b', 'c', 'd')
scala> val ints = List(0, 1, 2, 3)
scala> for (l <- letters; i <- ints if i % 2 == 0) yield l.toString + i
res0: List[java.lang.String] = List(a0, a2, b0, b2, c0, c2, d0, d2)

每个构造器都可以接受多个生成器/迭代器,应用过滤表达式并产生一个组合表达式。在Python中,(expr for v1 in gen1 if expr1 for v2 in gen2 if expr2) 大致相当于:

for v1 in gen1:
  if expr1:
    for v2 in gen2:
      if expr2:
        yield expr

在Scala中,for (v1 <- gen1 if expr1; v2 <- gen2 if expr2) yield expr大致等同于:

gen1.withFilter(expr1).flatMap(v1 => gen2.withFilter(expr2).map(v2 => expr))

如果您喜欢Python的for x in xs语法,那么您可能会喜欢Scala中的for表达式。

Scala具有一些额外的语法和转换技巧。从语法角度讲,可以使用花括号将for与语句放在不同的行上。您还可以执行值分配操作。

val res = for {
    i <- 1 to 20; i2 = i*i
    j <- 1 to 20; j2 = j*j
    k <- 1 to 20; k2 = k*k
    if i2 + j2 == k2
  } yield (i, j, k)

此外,v1 <- gen1 实际上执行了一个匹配 case v1 => gen1。如果没有匹配,这些元素将从迭代中被忽略。

scala> val list = List(Some(1), None, Some(2))
scala> for (Some(i) <- list) yield i
res2: List[Int] = List(1, 2)

我认为for在编程语言中有重要的地位。你正在阅读的书中甚至有一个完整的第23章节专门讲解它!


2
这真是一个非常棒的答案。我很高兴在接受任何答案之前等了几个小时。 - Rafe Kettler

28

是的,Scala for推导式(通常被称为)经常使用,但它们实际上只是一组特定方法的语法糖,许多人更喜欢直接调用这些方法而不使用语法糖。

为了更好地理解Scala for推导式,请参考这个问题。特别是,你会发现for (x <- xs) f(x)xs.foreach(x => f(x))是相同的。

现在,你提到你似乎很少使用foreach方法,但我要指出的是,Scala集合的几乎所有方法都可以(或可)仅使用foreach实现。请参见Traversable的文档--它的所有方法都可以仅使用foreach来实现。

请注意,Scala的yield与Python的yield没有任何相似之处--你可以查看那个问题。


3

Scala语言的for循环在支持嵌套迭代、过滤和转换方面非常强大且核心。我倾向于使用它而不是foreachmapfilter


3

foreach是一种函数式编程风格,而for则是一种命令式编程风格。如果您曾经使用过Lisp或Scheme,那么您已经熟悉函数式编程了。如果您还没有接触过,那么起初可能有些困惑。我建议您首先阅读闭包语法的相关资料,这些是您传递给类似foreach的匿名函数。一旦您理解了这一点,一切都会更加清晰明了。


3

以下内容可以大部分回答您的问题:

Scala的For Comprehensions

Scala Yield

总结来说:这主要是风格上的选择。个人而言,我更喜欢函数式方法,但在处理嵌套循环时,我更喜欢简洁的comprehensions。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接