【问题标题】:Scala: "map" vs "foreach" - is there any reason to use "foreach" in practice?Scala:“map”与“foreach”——在实践中是否有理由使用“foreach”?
【发布时间】:2014-11-03 12:08:19
【问题描述】:

在 Scala 集合中,如果想要遍历集合(不返回结果,即对集合的每个元素产生副作用),可以使用

final def foreach(f: (A) ⇒ Unit): Unit

final def map[B](f: (A) ⇒ B): SomeCollectionClass[B]

除了可能的惰性映射 (*) 之外,从最终用户的角度来看,我发现这些调用的差异为零:

myCollection.foreach { element =>
  doStuffWithElement(element);
}

myCollection.map { element =>
  doStuffWithElement(element);
}

鉴于我可以忽略地图输出的内容。当map 似乎包含foreach 的所有功能时,我想不出应该存在和使用两种不同方法的任何具体原因,事实上,如果一个智能编译器& VM 不会优化该集合对象的创建,因为它没有分配给任何东西,也没有在任何地方读取或使用。

所以,问题是 - 我是对的 - 没有理由在代码中的任何地方调用 foreach

注意事项:

(*) 惰性映射概念as throughly illustrated in this question 可能会改变一些事情并证明foreach 的使用是合理的,但据我所知,特别需要偶然发现LazyMap,正常

(**) 如果一个人不是使用一个集合,而是编写一个集合,那么人们很快就会发现for 理解语法实际上是一个事实生成“foreach”调用的语法糖,即这两行生成完全等效的代码:

for (element <- myCollection) { doStuffWithElement(element); }
myCollection.foreach { element => doStuffWithElement(element); }

因此,如果有人关心其他人使用具有for 语法的集合类,则可能仍希望实现foreach 方法。

【问题讨论】:

  • 最好使用foreach 而不是map 来区分副作用和非副作用功能。我不在乎编译器是否为另一个优化了一个。差异使代码的目的更加明显。
  • 为了比 LimbSoup 更进一步,我更喜欢直接使用for ..(无产量)而不是.foreach,因为它进一步(对我而言)显示了迭代之间的鸿沟for 副作用和序列转换。此外,假设被迭代的序列不是惰性的可能很危险。
  • 与你相反,如果当前的 Scala 编译器和/或 JVM 能够将 map 优化为 foreach 代码,会印象深刻结果未使用。在我看来,这是一个非常非常重要的优化。

标签: scala foreach scala-collections side-effects


【解决方案1】:

我能想到几个动机:

  1. foreachUnit 类型方法的最后一行时,您的编译器不会发出警告,但会发出map 警告(并且您需要-Ywarn-value-discard 开启)。有时你会使用map 得到warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses,但不会使用foreach
  2. 一般可读性 - 读者可以一眼就知道您正在改变某些状态而不返回某些内容,但是如果使用 map,则需要更多的认知资源来理解相同的操作
  3. 在 1 的基础上,您还可以在传递命名函数时进行类型检查,然后传入 mapforeach
  4. 使用foreach 不会建立新列表,因此会更高效(感谢@Vishnu)

【讨论】:

  • 如果我错了请纠正我,但Map 返回一个不可变对象,而foreach 的返回类型为Unit。因此,如果您的意图只是阅读内容,foreach 的内存效率会更高。对吗?
  • 是的,谢谢@Vishnu
  • 好吧,实际上我认为 map 返回的集合类型与它所应用的集合类型相同。所以如果你把它应用到一个可变集合上,你应该得到一个新的可变集合。
【解决方案2】:

如果构建器机器可以被优化掉,我会印象深刻。

scala> :pa
// Entering paste mode (ctrl-D to finish)

implicit val cbf = new collection.generic.CanBuildFrom[List[Int],Int,List[Int]] {
def apply() = new collection.mutable.Builder[Int, List[Int]] {
val b = new collection.mutable.ListBuffer[Int]
override def +=(i: Int) = { println(s"Adding $i") ; b +=(i) ; this }
override def clear() = () ; override def result() = b.result() }
def apply(from: List[Int]) = apply() }

// Exiting paste mode, now interpreting.

cbf: scala.collection.generic.CanBuildFrom[List[Int],Int,List[Int]] = $anon$2@e3cee7b

scala> List(1,2,3) map (_ + 1)
Adding 2
Adding 3
Adding 4
res1: List[Int] = List(2, 3, 4)

scala> List(1,2,3) foreach (_ + 1)

【讨论】:

  • 据我所见,CanBuildFrom 相关的隐式内容会在此处生成 JVM 级别的类层次结构,包括包含 += 方法的类,该方法实际上除了构造一个最终将被丢弃的变量(因此可以安全地跳过所有代码)。当然,某事的方法,尤其是当某事是明确的invokevirtual 调用时,不会被优化掉。
【解决方案3】:
scala> (1 to 5).iterator map println
res0: Iterator[Unit] = non-empty iterator

scala> (1 to 5).iterator foreach println
1
2
3
4
5

【讨论】:

  • 这正是我在 (*) 中提到的 - 它是惰性映射,这是所有迭代器的默认设置。
  • 所以你在这个问题中的断言是 foreachmap 是相同的,除非它们不是?
  • 我的断言是“除了明确概述的情况 () 和 (*) 之外,它们是相同的”,在我看来,这两种情况平均而言都相当罕见最终用户实践。
猜你喜欢
  • 2012-01-26
  • 2017-10-17
  • 1970-01-01
  • 2010-12-17
  • 2018-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多