我正在閱讀 scala-with-cats 書并按照它的練習進行操作。當我來到案例研究:資料驗證時,我遇到了一些問題。
這是我的整個代碼(與本書相同):
package org.scala.ch10.final_recap
import cats.Semigroup
import cats.data.Validated
import cats.data.Validated._
import cats.data.Kleisli
import cats.data.NonEmptyList
import cats.instances.either._
import cats.syntax.apply._
import cats.syntax.semigroup._
import cats.syntax.validated._
sealed trait Predicate[E, A] {
import Predicate._
def and(that: Predicate[E, A]): Predicate[E, A] =
And(this, that)
def or(that: Predicate[E, A]): Predicate[E, A] =
Or(this, that)
/**
* This part is for Kleislis
* @return
*/
def run(implicit s: Semigroup[E]): A => Either[E, A] =
(a: A) => this(a).toEither
def apply(a: A)(implicit s: Semigroup[E]): Validated[E, A] =
this match {
case Pure(func) =>
func(a)
case And(left, right) => (left(a), right(a)).mapN((_, _) => a)
case Or(left, right) =>
left(a) match {
case Valid(_) => Valid(a)
case Invalid(e1) =>
right(a) match {
case Valid(_) => Invalid(e1)
case Invalid(e2) => Invalid(e1 | | e2)
}
}
}
}
object Predicate {
final case class And[E, A](left: Predicate[E, A], right: Predicate[E, A]) extends Predicate[E, A]
final case class Or[E, A](left: Predicate[E, A], right: Predicate[E, A]) extends Predicate[E, A]
final case class Pure[E, A](func: A => Validated[E, A]) extends Predicate[E, A]
def apply[E, A](f: A => Validated[E, A]): Predicate[E, A] = Pure(f)
def lift[E, A](err: E, fn: A => Boolean): Predicate[E, A] = Pure(a => if(fn(a)) a.valid else err.invalid)
}
object FinalRecapPredicate {
type Errors = NonEmptyList[String]
def error(s: String): NonEmptyList[String] = NonEmptyList(s, Nil)
type Result[A] = Either[Errors, A]
type Check[A, B] = Kleisli[Result, A, B]
def check[A, B](func: A => Result[B]): Check[A, B] = Kleisli(func)
def checkPred[A](pred: Predicate[Errors, A]): Check[A, A] =
Kleisli[Result, A, A](pred.run)
def longerThan(n: Int): Predicate[Errors, String] =
Predicate.lift(
error(s"Must be longer than $n characters"),
str => str.length > n
)
val alphanumeric: Predicate[Errors, String] =
Predicate.lift(
error(s"Must be all alphanumeric characters"),
str => str.forall(_.isLetterOrDigit)
)
def contains(char: Char): Predicate[Errors, String] =
Predicate.lift(
error(s"Must contain the character $char"),
str => str.contains(char)
)
def containsOnce(char: Char): Predicate[Errors, String] =
Predicate.lift(
error(s"Must contain the character $char only once"),
str => str.count(_ == char) == 1
)
val checkUsername: Check[String, String] = checkPred(longerThan(3) and alphanumeric)
val splitEmail: Check[String, (String, String)] = check(_.split('@') match {
case Array(name, domain) =>
Right((name, domain))
case _ =>
Left(error("Must contain a single @ character"))
})
val checkLeft: Check[String, String] = checkPred(longerThan(0))
val checkRight: Check[String, String] = checkPred(longerThan(3) and contains('.'))
val joinEmail: Check[(String, String), String] =
check {
case (l, r) => (checkLeft(l), checkRight(r)).mapN(_ "@" _)
}
val checkEmail: Check[String, String] = splitEmail andThen joinEmail
final case class User(username: String, email: String)
def createUser(username: String, email: String): Either[Errors, User] =
(checkUsername.run(username),
checkEmail.run(email)).mapN(User)
def main(args: Array[String]): Unit = {
println(createUser("", "[email protected]@io"))
}
}
它假設代碼應該以錯誤訊息結束Left(NonEmptyList(Must be longer than 3 characters), Must contain a single @ character)但我實際上是Left(NonEmptyList(Must be longer than 3 characters))
顯然,它沒有按預期作業。它快速失敗而不是累積錯誤......如何解決這個問題?(我已經花了幾個小時,無法找到解決方法)
uj5u.com熱心網友回復:
這是“有問題”的部分:
def createUser(username: String, email: String): Either[Errors, User] =
(checkUsername.run(username),
checkEmail.run(email)).mapN(User)
您正在組合一個Results元組,其中
type Result[A] = Either[Errors, A]
這意味著您實際上是mapN在一對Eithers上執行 a ,這是Semigroupal型別類提供的操作。此操作不會累積結果。
造成這種情況的原因有很多,但我認為特別重要的一個原因是,如果我們發現自己使用了也恰好是 Monad 的 Semigroupal / Applicative,那么保留行為。為什么這是一個問題?因為 Monad 是排序操作,使每一步都依賴于前一步,并且具有“早期失敗”語意。當使用一些 Monad 時,人們可能希望在使用來自底層 Applicative 的構造時保留這些語意(每個 Monad 也是一個 Applicative)。在這種情況下,如果 Applicative 的實作使用“累積”語意而不是“早期失敗”語意,我們將破壞一些重要的法則,例如參考透明性。
您可以使用 的并行版本mapN,稱為parMapN,其契約保證實作將并行評估所有結果。這意味著絕對不能期望它具有“早期失敗”語意,并且在這種情況下累積結果很好。
請注意,Validated也會累積結果,通常在 aNonEmptyList或 a 中NonEmptyChain。這可能就是您希望看到累積結果的原因;唯一的問題是,您沒有Validated在代碼的“有問題”部分使用值,而是使用 raw Eithers。
下面是一些演示上述概念的簡單代碼:
import cats.data._
import cats.implicits._
val l1: Either[String, Int] = Left("foo")
val l2: Either[String, Int] = Left("bar")
(l1, l2).mapN(_ _)
// Left(foo)
(l1, l2).parMapN(_ _)
// Left(foobar)
val v1: ValidatedNel[String, Int] = l1.toValidatedNel
val v2: ValidatedNel[String, Int] = l2.toValidatedNel
(v1, v2).mapN(_ _)
// Invalid(NonEmptyList(foo, bar))
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/327832.html
上一篇:基于另一列范圍的子集中值的排名
