Scala: "карта" против "foreach" - есть ли какая-нибудь причина использовать "foreach" на практике?

В коллекциях 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 где угодно в одном коде?

Примечания:

(*) Концепция ленивого отображения как показано на этом вопросе, может немного изменить ситуацию и оправдать использование foreach, но насколько я может видеть, что нужно конкретно наткнуться на LazyMap, normal

(**) Если вы не используете коллекцию, но пишете ее, то быстро натыкаетесь на то, что синтаксис синтаксиса синтаксиса for на самом деле является синтаксическим сахаром, который генерирует вызов "foreach", то есть эти две строки генерировать полностью эквивалентный код:

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

Поэтому, если кто-то заботится о том, чтобы другие люди использовали этот класс коллекции с синтаксисом for, все же можно было бы реализовать метод foreach.

Ответ 1

Я могу придумать пару мотивов:

  • Когда foreach является последней строкой метода, имеющего тип Unit, ваш компилятор не выдаст предупреждение, но будет с map (и вам нужно -Ywarn-value-discard on). Иногда вы получаете warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses с помощью map, но не с foreach.
  • Общая читаемость - читатель может знать, что ваш мутирующий некоторый штат, не возвращая что-то с первого взгляда, но для понимания той же операции потребуется больше когнитивных ресурсов, если map был использован
  • В дополнение к 1. вы также можете иметь проверку типов при передаче именованных функций, затем в map и foreach

Ответ 2

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

Ответ 3

Я был бы впечатлен, если бы машины-строители могли быть оптимизированы.

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]] = [email protected]

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)