Разница между flatMap, flatTap, evalMap и evalTap

В библиотеке Scala fs2 для функциональных потоков:

Я пытаюсь понять разницу между flatMap, flatTap, evalMap и evalTap. Кажется, что все они выполняют одно и то же — преобразование значений потока.

В чем разница и когда каждый из них следует использовать?


person Lev Denisov    schedule 12.11.2019    source источник
comment
Вы можете найти полезные примеры Cats github чтобы понять общую идею, чем flatTap отличается от flatMap :)   -  person Bartłomiej Szałach    schedule 12.11.2019


Ответы (1)


Традиционно tap подобные функции позволяют вам наблюдать (или заглядывать) за элементами в потоке, но отбрасывают результат эффекта наблюдения. Например, в fs2 вы можете увидеть подпись для evalTap:

def evalTap[F2[x] >: F[x]](f: (O) ⇒ F2[_])(implicit arg0: Functor[F2]): Stream[F2, O]

Обратите внимание, что f является функцией из O => F2[_], что означает «вы берете значение O и возвращаете тип эффекта F2, для которого существует Functor», но это не влияет на возвращаемый тип потока, который по-прежнему O.

Например, если мы хотим передать элементы потока на консоль, мы можем сделать:

import cats.effect.{ExitCode, IO, IOApp}
import cats.implicits._

object Test extends IOApp {
  override def run(args: List[String]): IO[ExitCode] = {
    fs2
      .Stream(1, 2, 3)
      .covary[IO]
      .evalTap(i => IO(println(i)))
      .map(_ + 1)
      .compile
      .drain
      .as(ExitCode.Success)
  }
}

Это даст 1 2 3.

Вы можете видеть, что мы выводим каждый элемент потока на консоль, используя evalTap, где у нас есть эффект типа IO[Unit], но мы можем сразу же map каждый такой элемент на следующем шаге конвейера, так как это не повлияло на тип результата потока.

Я не смог найти flatTap, но я думаю, что они в целом одинаковы в fs2 (https://github.com/functional-streams-for-scala/fs2/issues/1177)

С другой стороны, такая функция, как flatMap, вызывает изменение возвращаемого типа потока. Мы видим подпись:

def flatMap[F2[x] >: F[x], O2](f: O => Stream[F2, O2]): Stream[F2, O2] =

Обратите внимание, что в отличие от evalTap результатом выполнения f является O2, который также закодирован в возвращаемом типе. Если мы возьмем тот же пример, что и выше:

fs2
  .Stream(1, 2, 3)
  .covary[IO]
  .flatMap(i => fs2.Stream(IO(println(i))))
  .map(_ + 1)
  .compile
  .drain
  .as(ExitCode.Success)

Это больше не будет компилироваться, так как flatMap возвращает Stream[IO, Unit], а это означает, что выполнение println и тот факт, что он возвращает Unit, напрямую влияет на нижестоящие комбинаторы.

evalMap — это псевдоним для flatMap, который позволяет вам опустить перенос типа Stream и обычно реализуется в терминах flatMap:

def evalMap[F2[x] >: F[x], O2](f: O => F2[O2]): Stream[F2, O2] =
  flatMap(o => Stream.eval(f(o)))

Что несколько удобнее в использовании.

person Yuval Itzchakov    schedule 12.11.2019