Scala: комбинируйте либо для всего списка, либо для каждого элемента

У меня есть список либо, который представляет ошибку:

type ErrorType = List[String]
type FailFast[A] = Either[ErrorType, A]

import cats.syntax.either._
val l = List(1.asRight[ErrorType], 5.asRight[ErrorType])

Если все они верны, я хочу получить список [A], в данном случае — List[Int]

Если какой-то Either остался, я хочу объединить все ошибки всех и вернуть его.

Я нашел похожую тему на [Как уменьшить Seq[Either[A,B]] до Both[A,Seq[B]]

Но это было довольно давно. Например, один из ответов предлагает использовать partitionMap, который я не могу найти в данный момент. Вероятно, есть лучшее, более элегантное решение. Пример со scala-cats был бы отличным.

Как я хотел бы его использовать:

for {
  listWithEihers <- someFunction
  //if this list contains one or more errors, return Left[List[String]]
  //if everything is fine, convert it to:
  correctItems <- //returns list of List[Int] as right
} yield correctItems

Тип возвращаемого значения этого for-comprehension должен быть:

Either[List[String], List[Int]]

Похоже, вы хотите использовать Validated вместо Either.

Luis Miguel Mejía Suárez 22.05.2019 15:54

@LuisMiguelMejíaSuárez, я использовал его в тех случаях, когда заранее знаю количество возвращаемых функций. Либо я могу объединить их с Semigroupal.tupleN. Но не могу понять, как использовать его в этом контексте. Количество элементов списка неизвестно.

Alexandr 22.05.2019 15:59

Может что-то вроде listWithEihers.foldMap(e => Validated.fromEither(e).toValidatedNec).

Luis Miguel Mejía Suárez 22.05.2019 16:10

@LuisMiguelMejíaSuárez, foldMap, что это? Не удается найти его среди методов списка или либо

Alexandr 22.05.2019 16:15

О, извините, это метод расширения, предоставляемый import cats.syntax.foldable._ любому типу C[_], который имеет экземпляр Foldable[C]. Сигнатура foldMap[A, B](f: A => B)(implicit M: Monoid[B]): B В основном она отображает значения в коллекции и складывает их вместе, используя моноид, а моноид Validated накапливает ошибки и значения.

Luis Miguel Mejía Suárez 22.05.2019 16:29

Вы не хотите его складывать, вы хотите traverse или sequence с Traverse.

Andrey Tyukin 22.05.2019 17:12
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
3
6
2 102
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как упоминает @Luis в комментариях, ValidatedNel — это то, что вы ищете:

import cats.data.{ Validated, ValidatedNel }
import cats.implicits._

type ErrorType = String

def combine(listWithEither: List[Either[ErrorType, Int]]):ValidatedNel[ErrorType, List[Int]] =
      listWithEither.foldMap(e => Validated.fromEither(e).map(List(_)).toValidatedNel)

      val l1 = List[Either[ErrorType, Int]](Right(1), Right(2), Right(3))
      val l2 = List[Either[ErrorType, Int]](Left("Incorrect String"), Right(2), Left("Validation error"))

println(combine(l1))
// Displays Valid(List(1, 2, 3))

println(combine(l2))
// Displays Invalid(NonEmptyList(Incorrect String, Validation error))

Вы можете преобразовать финал обратно в Either, используя .toEither, но ValidatedNel лучше подходит для накопления ошибок, а Either больше подходит для fail fast ошибок.

О верно. Я тоже забыл превратить ветку valid в Список.

Luis Miguel Mejía Suárez 22.05.2019 17:12
Ответ принят как подходящий

Как уже упоминалось в комментариях, Either хорош для безотказного поведения. Для накопления нескольких ошибок вам, вероятно, понадобится что-то вроде Validated. Кроме того:

  • Список доступен для просмотра (имеет экземпляр Traverse)
  • Утверждено применимо
  • Validated.fromEither отображает Either[List[String], X] в Validated[List[String], X], это именно то, что вам нужно в качестве функции в traverse.

Поэтому вы можете попробовать:

  • l.traverse(Validated.fromEither) если у вас все в порядке с Validated
  • l.traverse(Validated.fromEither).toEither, если вы действительно хотите Either в конце.

Полный пример со всеми импортами:

import cats.data.Validated
import cats.syntax.validated._
import cats.syntax.either._
import cats.syntax.traverse._
import cats.instances.list._
import cats.Traverse
import scala.util.Either

type ErrorType = List[String]
type FailFast[A] = Either[ErrorType, A]
val l: List[Either[ErrorType, Int]] = List(1.asRight[ErrorType], 5.asRight[ErrorType])

// solution if you want to keep a `Validated`
val validatedList: Validated[ErrorType, List[Int]] =
  l.traverse(Validated.fromEither)

// solution if you want to transform it back to `Either`
val eitherList: Either[ErrorType, List[Int]] =    
  l.traverse(Validated.fromEither).toEither

Другие вопросы по теме