Как суммировать поля элементов коллекции, не отображая их сначала (например, foldLeft/reduceLeft)?

Рассмотрим этот класс:

 case class Person(val firstName: String, val lastName: String, age: Int)
 val persons = Person("Jane", "Doe", 42) :: Person("John", "Doe", 45) :: 
               Person("Joe", "Doe", 43) :: Person("Doug", "Don", 65) :: 
               Person("Darius", "Don", 24) :: Person("Dora", "Don", 20) :: 
               Person("Dane", "Dons", 29) :: Nil

Чтобы получить сумму возраста всех лиц, я могу написать код вроде:

persons.foldLeft(0)(_ + _.age)

Но если я хочу использовать sum, мне нужно сначала сопоставить значение, и код выглядит следующим образом:

persons.map(_.age).sum

Как я могу использовать метод sum, не создавая промежуточную коллекцию?

(Я знаю, что такая "оптимизация", скорее всего, не имеет реальной разницы в производительности, когда не выполняется в замкнутом цикле, и я также знаю о ленивых представлениях и т.д.)

Возможно ли иметь код типа

persons.sum(_.age)

что делает foldLeft/reduceLeft?

Ответ 1

Метод sum в библиотеке работает не так, но вы можете написать свой собственный, который:

def mySum[T, Res](f: T => Res, seq: TraversableOnce[T])(implicit num: Numeric[Res]) = 
  seq.foldLeft(num.zero)((acc, b) => num.plus(acc, f(b)))

Вы также можете добавить неявное преобразование, чтобы вы могли называть его как seq.sum(f) вместо mySum(f, seq) (для предотвращения конфликтов может потребоваться другое имя, чем sum):

case class SumTraversableOnce[T](val seq: TraversableOnce[T]) { 
  def sum[Res](f: T => Res)(implicit num: Numeric[Res]) = mySum(f, seq)(num) 
}

implicit def toSumTraversableOnce[T](seq: TraversableOnce[T]) = 
  SumTraversableOnce(seq)

или, поскольку Scala 2.10,

implicit class SumTraversableOnce[T](val seq: TraversableOnce[T]) { 
  def sum[Res](f: T => Res)(implicit num: Numeric[Res]) = mySum(f, seq)(num) 
}

Ответ 2

Вы ответили сами. Просто используйте view:

persons.view.map(_.age).sum

Чтобы убедить себя, исследуя рабочий процесс:

persons.view.map { p =>
  println("invoking age")
  p.age
}.map { x =>
  println("modifing age")
  x + 0
}.sum

Vs:

persons.map { p =>
  println("invoking age")
  p.age
}.map { x =>
  println("modifing age")
  x + 0
}.sum