Функция, которую вы ищете, обычно называется zipWith
. К сожалению, его нет в стандартных библиотеках, но его довольно легко написать:
def zipWith[A,B,C](f: (A,B) => C, a: Iterable[A], b: Iterable[B]) =
new Iterable[C] {
def elements = (a.elements zip b.elements) map f.tupled
}
Это будет проходить только один раз, поскольку реализации для zip
и map
на итераторах полностью ленивы.
Но зачем останавливаться на Iterable
? Это имеет еще более общую форму. Мы могли бы объявить интерфейс для всех структур данных, которые могут быть заархивированы таким образом.
trait Zip[F[_]] {
def zipWith[A,B,C](f: (A,B) => C, a: F[A], b: F[B]): F[C]
}
Например, мы можем архивировать функции:
trait Reader[A] {
type Read[B] = (A => B)
}
def readerZip[T] = new Zip[Reader[T]#Read] {
def zipWith[A,B,C](f: (A,B) => C, a: T => A, b: T => B): T => C =
(t: T) => f(a(t),b(t))
}
Оказывается, есть еще более общее выражение этого типа. В общем случае конструкторами типов, которые допускают реализацию этого интерфейса, являются аппликативные функторы
trait Applicative[F[_]] {
def pure[A](a: A): F[A]
def map[A,B](f: A => B, a: F[A]): F[B]
def ap[A,B](f: F[A => B], a: F[A]): F[B]
}
В этом случае реализация zipWith:
def zipWith[F[_],A,B,C](f: A => B => C, a: F[A], b: F[B])
(implicit m: Applicative[F]) =
m.ap(m.map(f,a), b)
Это обобщает функции любой арности:
m.ap(m.ap(m.ap(m.map(f,a), b), c), d)
Библиотека Scalaz предоставляет экземпляры Applicative для большого количества структур данных в стандартной библиотеке. Также предусмотрен удобный синтаксис для ap
. В Scalaz эта функция называется <*>
:
def zipWith[F[_]:Applicative,A,B,C](f: A => B => C, a: F[A], b: F[B]) =
(a map f) <*> b