简体   繁体   中英

Scala - how to validate every String in list using Validated monad from cats?

I have a list of Strings which I need to validate if they are match some pattern:

def validate(strings: List[String]): Either[WrongStringError, List[String]] = {
    strings.foreach(s => checkString(s))
    Right(strings)
  }

private def checkString(s: String): Validated[WrongStringError, String] = {
  val pattern = .... //some regex
  Either.catchNonFatal(s.matches(pattern))
    .left
    .map(_ => WrongStringError(s"Given string: ${s} is not valid."))
  }.toValidated

But, what I want to achieve is to accomulate all errors together. If for example 3 of 5 strings are incorrect I want return error with them to the user. I thought of using Validated monad from cats but I did not get result. How can I validate list of strings and return list of errors for those which are incorrect?

I thought of using Validated monad from cats but I did not get result.

Well, the idea of things like Validated (or any FP data structure) is to compose values together.

However, you are not composing anything here, you are just calling a foreach , so in case of a failure you would not even receive the errors, you will always receive a Right .

Also, if you want to collect all errors, you would need to use some kind of (non-empty) container like NonEmptyChain .

Finally, this common pattern of I have a collection of values and a function that transform those values into an effectual value and I would like to collect / combine all those effects into a single effect of a list is called traverse .

Bonus points, this common pattern of using an isomorphic effect type that looks exactly like my normal effect just that it is only an Applicative instead of a Monad in order to provide a different (parallel) behaviour is so common, that cats introduced the Parallel typeclass and combinators (eg parTraverse ) to avoid the boilerplate.

In any case, this is how the code would look like:

import cats.data.NonEmptyChain
import cats.syntax.all._

type Error = String

def validate(strings: List[String]): Either[NonEmptyChain[Error], List[String]] =
  strings.parTraverse { s =>
    checkString(s).toEitherNec
  }

def checkString(s: String): Either[Error, String] = {
  val pattern = ""
  Either.cond(
    test = s.matches(pattern),
    right = s,
    left = "Given string: ${s} is not valid."
  )
}

BTW, I would recommend you to study more about FP.
I may recommend you the talk "Functional Programming with Effects" by Rob Norris and the (free) book "Scala with Cats".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM