Я собираюсь предположить, что вам нужны все ошибки и все успешные результаты. Вот возможная реализация:
class Foo[F[_]: Applicative, A](find: String => F[IorNel[Error, A]]) {
def findMultiple(keys: List[String]): F[IorNel[Error, List[A]]] = {
keys.map(find).sequence.map { nelsList =>
nelsList.map(nel => nel.map(List(_)))
.reduceOption(_ |+| _).getOrElse(Nil.rightIor)
}
}
}
Давайте разберемся с ней:
Мы будем пытаться "перевернуть" List[IorNel[Error, A]]
в IorNel[Error, List[A]]
. Однако, выполнив keys.map(find)
, мы получим List[F[IorNel[...]]]
, поэтому нам нужно сначала «перевернуть» его аналогичным образом. Это можно сделать, используя .sequence
для результата, и это то, что заставляет ограничение F[_]: Applicative
.
NB Applicative[Future]
доступно, когда существует неявная ExecutionContext
область действия. Вы также можете избавиться от F
и напрямую использовать Future.sequence
.
Теперь у нас есть F[List[IorNel[Error, A]]]
, поэтому мы хотим map
внутреннюю часть для преобразования nelsList
, которое мы получили. Вы можете подумать, что sequence
также может использоваться там, но это не может - он имеет поведение «короткое замыкание при первой ошибке», поэтому мы потеряем все успешные значения. Давайте попробуем использовать |+|
.
Ior[X, Y]
имеет экземпляр Semigroup
, когда оба X
и Y
имеют его. Так как мы используем IorNel
, X = NonEmptyList[Z]
, и это удовлетворено. Для Y = A
- вашего типа домена - он может быть недоступен.
Но мы не хотим объединять все результаты в один A
, нам нужно Y = List[A]
(в котором также всегда есть полугруппа). Таким образом, мы берем каждое IorNel[Error, A]
, которое у нас есть, и map
A
в синглтоне List[A]
:
nelsList.map(nel => nel.map(List(_)))
Это дает нам List[IorNel[Error, List[A]]
, которое мы можем уменьшить. К сожалению, поскольку у Иора нет Monoid
, мы не можем использовать удобный синтаксис. Таким образом, с коллекциями stdlib можно сделать .reduceOption(_ |+| _).getOrElse(Nil.rightIor)
.
. Это можно улучшить, выполнив несколько вещей:
x.map(f).sequence
эквивалентно выполнению x.traverse(f)
- Мы можем требовать, чтобы ключи были непустыми заранее и также давали непустой результат.
Последний шаг дает нам Reducible
экземпляр для коллекции, позволяя намсократите все, выполнив reduceMap
class Foo2[F[_]: Applicative, A](find: String => F[IorNel[Error, A]]) {
def findMultiple(keys: NonEmptyList[String]): F[IorNel[Error, NonEmptyList[A]]] = {
keys.traverse(find).map { nelsList =>
nelsList.reduceMap(nel => nel.map(NonEmptyList.one))
}
}
}
Конечно, вы можете сделать из этого однострочную строку:
keys.traverse(find).map(_.reduceMap(_.map(NonEmptyList.one)))
Или же вы можете проверить не пустоту внутри:
class Foo3[F[_]: Applicative, A](find: String => F[IorNel[Error, A]]) {
def findMultiple(keys: List[String]): F[IorNel[Error, List[A]]] = {
NonEmptyList.fromList(keys)
.map(_.traverse(find).map { _.reduceMap(_.map(List(_))) })
.getOrElse(List.empty[A].rightIor.pure[F])
}
}