Эффект кошки - параллельная композиция независимых эффектов
Я хочу объединить несколько IO
значения, которые должны работать независимо друг от друга параллельно.
val io1: IO[Int] = ???
val io2: IO[Int] = ???
На мой взгляд, у меня есть варианты:
- Используйте волокна кошачьего эффекта с рисунком вилки
val parallelSum1: IO[Int] = for { fiber1 <- io1.start fiber2 <- io2.start i1 <- fiber1.join i2 <- fiber2.join } yield i1 + i2
- Использовать
Parallel
экземпляр дляIO
сparMapN
(или один из его братьев и сестер, таких какparTraverse
,parSequence
,parTupled
так далее)val parallelSum2: IO[Int] = (io1, io2).parMapN(_ + _)
Не уверен насчет плюсов и минусов каждого подхода, и когда я должен выбрать один над другим. Это становится еще сложнее, если абстрагироваться от типа эффекта IO
(стиль без тегов):
def io1[F[_]]: F[Int] = ???
def io2[F[_]]: F[Int] = ???
def parallelSum1[F[_]: Concurrent]: F[Int] = for {
fiber1 <- io1[F].start
fiber2 <- io2[F].start
i1 <- fiber1.join
i2 <- fiber2.join
} yield i1 + i2
def parallelSum2[F[_], G[_]](implicit parallel: Parallel[F, G]): F[Int] =
(io1[F], io2[F]).parMapN(_ + _)
Parallel
Класс типов требует 2-х конструкторов типов, что делает его несколько более громоздким в использовании без границ контекста и с дополнительным неопределенным параметром типа G[_]
Ваше руководство ценится:)
Amitay
1 ответ
Я хочу объединить несколько значений ввода-вывода, которые должны работать независимо параллельно.
То, как я это вижу, для того, чтобы выяснить, "когда я использую что?", Нам нужно вернуть обсуждение "старая параллель против параллельной", которое в основном сводится к (цитируя принятый ответ):
Параллельность - это когда две или более задач могут запускаться, выполняться и завершаться в перекрывающиеся периоды времени. Это не обязательно означает, что они оба будут работать одновременно. Например, многозадачность на одноядерной машине.
Параллелизм - это когда задачи выполняются буквально одновременно, например, на многоядерном процессоре.
Нам часто нравится предоставлять пример параллелизма, когда мы выполняем операции, подобные вводу-выводу, такие как создание беспроводного вызова или общение с диском.
Вопрос в том, какой из них вы хотите, когда говорите, что хотите выполнить "параллельно", это первый или второй?
Если мы ссылаемся на первое, то с помощью Concurrent[F]
оба передают намерение с помощью подписи и обеспечивают правильную семантику исполнения. Если это последнее, и мы, например, хотим обрабатывать набор элементов параллельно, тогда перейдем к Parallel[F, G]
было бы лучшим решением.
Это часто сбивает с толку, когда мы думаем о семантике этого относительно IO
потому что у него есть оба экземпляра для Parallel
а также Concurrent
и мы в основном используем его для непрозрачного определения побочных эффектов.
Как примечание, причина Parallel
принимая два одинарных конструкторов типа из-за того, что M
(в Parallel[M[_], F[_]]
) всегда Monad
экземпляр, и нам нужен способ доказать, что Монада имеет Applicative[F]
Например, для параллельных выполнений, потому что когда мы думаем о Monad, мы всегда говорим о семантике последовательного выполнения.