使用参数化验证结果验证的 Scala Cats
Scala Cats Validated with Parameterized Validation Result
我正在按照 https://typelevel.org/cats/datatypes/validated.html 中的示例进行操作,但有一点不同:NEL 中的元素类型不是 DomainValidation 而是 DomainValidation[A],我不确定如何执行此操作。以下是我的尝试,但在使用类型时遇到了问题。这可能既是一个 Scala 问题,也是一个经过 Cats 验证的问题。
import cats.data._
import cats.implicits._
sealed trait Widget
case object WidgetA extends Widget
case object WidgetB extends Widget
object Widget{
val values = List(WidgetA, WidgetB)
def fromString(s: String): Option[Widget] = values.filter(_.toString == s).headOption
}
case class Order( quantity: Float, widget: Widget )
trait MyValidation[A] {
def errorMessage: String
def is: A
}
type ValidationResult[A,B] = ValidatedNel[MyValidation[A], B]
case class WidgetQuantityIsNegative(is: Float) extends MyValidation[Float] {
def errorMessage = s"$is widget quantity is negative"
}
case class WidgetTypeIsInvalid(is: String) extends MyValidation[String] {
def errorMessage = s"$is is not a valid widget type"
}
def validateQuantity(q: Float): ValidationResult[Float, Float] =
if(q >= 0) q.validNel else WidgetQuantityIsNegative(q).invalidNel
/*
Here I would like to validate the string.
If it represents a valid Widget, return the valid Widget
else return the invalid */
def validateWidgetType(s: String) =
Widget.fromString(s) map {widget => widget.validNel } getOrElse WidgetTypeIsInvalid.invalidNel
def validateOrder( quantity: Float, s: String ): ValidationResult[MyValidation[Any], Order] = // NOT SURE IF ANY IS THE RIGHT TYPE
(
(validateQuantity(quantity)),
(validateWidgetType(s))
).mapN[Order](Order.apply) // THIS DOES NOT COMPILE
我稍微调整了你的代码并编译:
import cats.data._
import cats.implicits._
sealed trait Widget
case object WidgetA extends Widget
case object WidgetB extends Widget
object Global {
object Widget {
val values = List(WidgetA, WidgetB)
def fromString(s: String): Option[Widget] = values.find(_.toString == s)
}
case class Order(quantity: Float, widget: Widget)
trait MyValidation[+A] {
def errorMessage: String
def is: A
}
type ValidationResult[A, B] = ValidatedNel[MyValidation[A], B]
case class WidgetQuantityIsNegative(is: Float) extends MyValidation[Float] {
def errorMessage = s"$is widget quantity is negative"
}
case class WidgetTypeIsInvalid(is: String) extends MyValidation[String] {
def errorMessage = s"$is is not a valid widget type"
}
def validateQuantity(q: Float): ValidationResult[Float, Float] =
if (q >= 0) q.validNel else WidgetQuantityIsNegative(q).invalidNel
/*
Here I would like to validate the string.
If it represents a valid Widget, return the valid Widget
else return the invalid string */
def validateWidgetType(s: String): ValidationResult[String, Widget] =
Widget.fromString(s) map { widget => widget.validNel } getOrElse WidgetTypeIsInvalid(s).invalidNel
def validateOrder(quantity: Float, s: String): ValidationResult[Any, Order] =
(
validateQuantity(quantity),
validateWidgetType(s)
).mapN[Order](Order(_, _)) // Order.apply without _ works too but would add one more red line in intellij
}
基本上我所做的就是标记 MyValidation
是协变的并将 validateWidgetType
和 validateQuantity
都固定为 return 相同的类型
关于协方差的注意事项:
当我们删除协方差注释时,会出现以下编译器消息:
Error:(50, 18) type mismatch;
found : cats.data.Validated[cats.data.NonEmptyList[cats.Global.MyValidation[_ >: String with Float]],cats.Global.Order]
required: cats.Global.ValidationResult[Any,cats.Global.Order]
(which expands to) cats.data.Validated[cats.data.NonEmptyList[cats.Global.MyValidation[Any]],cats.Global.Order]
Note: cats.data.NonEmptyList[cats.Global.MyValidation[_ >: String with Float]] <: Any, but class Validated is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
).mapN[Order](Order.apply)
这或多或少表明既是 Float
又是 String
的子类型的具体类型不完全是 Any
而是 Any
的子类型
我正在按照 https://typelevel.org/cats/datatypes/validated.html 中的示例进行操作,但有一点不同:NEL 中的元素类型不是 DomainValidation 而是 DomainValidation[A],我不确定如何执行此操作。以下是我的尝试,但在使用类型时遇到了问题。这可能既是一个 Scala 问题,也是一个经过 Cats 验证的问题。
import cats.data._
import cats.implicits._
sealed trait Widget
case object WidgetA extends Widget
case object WidgetB extends Widget
object Widget{
val values = List(WidgetA, WidgetB)
def fromString(s: String): Option[Widget] = values.filter(_.toString == s).headOption
}
case class Order( quantity: Float, widget: Widget )
trait MyValidation[A] {
def errorMessage: String
def is: A
}
type ValidationResult[A,B] = ValidatedNel[MyValidation[A], B]
case class WidgetQuantityIsNegative(is: Float) extends MyValidation[Float] {
def errorMessage = s"$is widget quantity is negative"
}
case class WidgetTypeIsInvalid(is: String) extends MyValidation[String] {
def errorMessage = s"$is is not a valid widget type"
}
def validateQuantity(q: Float): ValidationResult[Float, Float] =
if(q >= 0) q.validNel else WidgetQuantityIsNegative(q).invalidNel
/*
Here I would like to validate the string.
If it represents a valid Widget, return the valid Widget
else return the invalid */
def validateWidgetType(s: String) =
Widget.fromString(s) map {widget => widget.validNel } getOrElse WidgetTypeIsInvalid.invalidNel
def validateOrder( quantity: Float, s: String ): ValidationResult[MyValidation[Any], Order] = // NOT SURE IF ANY IS THE RIGHT TYPE
(
(validateQuantity(quantity)),
(validateWidgetType(s))
).mapN[Order](Order.apply) // THIS DOES NOT COMPILE
我稍微调整了你的代码并编译:
import cats.data._
import cats.implicits._
sealed trait Widget
case object WidgetA extends Widget
case object WidgetB extends Widget
object Global {
object Widget {
val values = List(WidgetA, WidgetB)
def fromString(s: String): Option[Widget] = values.find(_.toString == s)
}
case class Order(quantity: Float, widget: Widget)
trait MyValidation[+A] {
def errorMessage: String
def is: A
}
type ValidationResult[A, B] = ValidatedNel[MyValidation[A], B]
case class WidgetQuantityIsNegative(is: Float) extends MyValidation[Float] {
def errorMessage = s"$is widget quantity is negative"
}
case class WidgetTypeIsInvalid(is: String) extends MyValidation[String] {
def errorMessage = s"$is is not a valid widget type"
}
def validateQuantity(q: Float): ValidationResult[Float, Float] =
if (q >= 0) q.validNel else WidgetQuantityIsNegative(q).invalidNel
/*
Here I would like to validate the string.
If it represents a valid Widget, return the valid Widget
else return the invalid string */
def validateWidgetType(s: String): ValidationResult[String, Widget] =
Widget.fromString(s) map { widget => widget.validNel } getOrElse WidgetTypeIsInvalid(s).invalidNel
def validateOrder(quantity: Float, s: String): ValidationResult[Any, Order] =
(
validateQuantity(quantity),
validateWidgetType(s)
).mapN[Order](Order(_, _)) // Order.apply without _ works too but would add one more red line in intellij
}
基本上我所做的就是标记 MyValidation
是协变的并将 validateWidgetType
和 validateQuantity
都固定为 return 相同的类型
关于协方差的注意事项:
当我们删除协方差注释时,会出现以下编译器消息:
Error:(50, 18) type mismatch;
found : cats.data.Validated[cats.data.NonEmptyList[cats.Global.MyValidation[_ >: String with Float]],cats.Global.Order]
required: cats.Global.ValidationResult[Any,cats.Global.Order]
(which expands to) cats.data.Validated[cats.data.NonEmptyList[cats.Global.MyValidation[Any]],cats.Global.Order]
Note: cats.data.NonEmptyList[cats.Global.MyValidation[_ >: String with Float]] <: Any, but class Validated is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
).mapN[Order](Order.apply)
这或多或少表明既是 Float
又是 String
的子类型的具体类型不完全是 Any
而是 Any
的子类型