Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cats Validated with mapN

I'm a beginner with Cats. I have an error with Validated cats. I use a list accumulator like that :

case class Type(
 name: String,
 pattern: String,
 primitiveType: PrimitiveType = PrimitiveType.string,
 sample: Option[String] = None,
 comment: Option[String] = None,
 stat: Option[Stat] = None
) {
 type ValidationResult[A] = Validated[List[String], A]

 def checkValidity(): ValidationResult[Boolean] = {
  val errorList: mutable.MutableList[String] = mutable.MutableList.empty

  val patternIsValid = Try {
   primitiveType match {
    case PrimitiveType.struct =>
    case PrimitiveType.date =>
      new SimpleDateFormat(pattern)
    case PrimitiveType.timestamp =>
      pattern match {
        case "epoch_second" | "epoch_milli" =>
        case _ if PrimitiveType.formatters.keys.toList.contains(pattern) =>
        case _ =>
          DateTimeFormatter.ofPattern(pattern)
      }
    case _ =>
      Pattern.compile(pattern)
   }
 }
 if (patternIsValid.isFailure)
  errorList += s"Invalid Pattern $pattern in type $name"
 val ok = sample.forall(this.matches)
 if (!ok)
  errorList += s"Sample $sample does not match pattern $pattern in type $name"
 if (errorList.nonEmpty)
  Invalid(errorList.toList)
 else
  Valid(true)
}
}

When I use this function with my case class Types :

case class Types(types: List[Type]) {

 type ValidationResult[A] = Validated[List[String], A]

 def checkValidity(): ValidationResult[Boolean] = {
   val typeNames = types.map(_.name)
   val dup: ValidationResult[Boolean] =
   duplicates(typeNames, s"%s is defined %d times. A type can only be defined once.")
  (dup,types.map(_.checkValidity()).sequence).mapN((_,_) => true)
 }
}

I have this error

Error:(29, 39) Cannot prove that cats.data.Validated[List[String],Boolean] <:< G[A].
(dup,types.map(_.checkValidity()).sequence: _*).mapN((_,_) => true)

Can you help me to resolve this error?

Thanks for your help.

like image 648
Robert25 Avatar asked Feb 11 '19 17:02

Robert25


1 Answers

Many years ago I wrote a long blog post about the underlying issue you're running into here, if you're interested in the history or some old workarounds, but luckily now the solution is much easier (assuming you're on Scala 2.11 or 2.12): just add -Ypartial-unification to your Scala compiler options. If you're using sbt, for example, that might look like this:

scalacOptions += "-Ypartial-unification"

And you're done.

If for some reason you can't add the compiler option, you'll have to provide some type parameters explicitly. Here's a quick simplified version:

import cats.data.Validated, cats.implicits._


case class Foo(i: Int) {
  type ValidationResult[A] = Validated[List[String], A]

  def check: ValidationResult[Boolean] =
    if (i < 0) Validated.invalid(List("bad")) else Validated.valid(true)
}

case class Foos(values: List[Foo]) {
  type ValidationResult[A] = Validated[List[String], A]

  def dup: ValidationResult[Unit] = Validated.valid(())
  def check: ValidationResult[Boolean] =
    (dup, values.map(_.check).sequence).mapN((_, _) => true)
}

That will fail with the error you saw (assuming you haven't added -Ypartial-unification):

<console>:22: error: Cannot prove that cats.data.Validated[List[String],Boolean] <:< G[A].
           (dup, values.map(_.check).sequence).mapN((_, _) => true)
                                     ^

To fix it you can write the following:

case class Foos(values: List[Foo]) {
  type ValidationResult[A] = Validated[List[String], A]

  def dup: ValidationResult[Unit] = Validated.valid(())
  def check: ValidationResult[Boolean] =
    (dup, values.map(_.check).sequence[ValidationResult, Boolean]).mapN((_, _) => true)
}

I think you could also probably just move the type alias to the package level, but I'm not 100% sure about that and am not motivated to check, sorry.

One footnote: any time you have map and then sequence, you can make things a little faster by using traverse instead:

  def check: ValidationResult[Boolean] =
    (dup, values.traverse[ValidationResult, Boolean](_.check)).mapN((_, _) => true)

Again you can drop the type parameters and let type inference figure them out if you have -Ypartial-unification enabled.

like image 158
Travis Brown Avatar answered Sep 22 '22 12:09

Travis Brown