Вот пример из книги Scala с кошками :
object Ex {
import cats.data.Validated
type FormData = Map[String, String]
type FailFast[A] = Either[List[String], A]
def getValue(name: String)(data: FormData): FailFast[String] =
data.get(name).toRight(List(s"$name field not specified"))
type NumFmtExn = NumberFormatException
import cats.syntax.either._ // for catchOnly
def parseInt(name: String)(data: String): FailFast[Int] =
Either.catchOnly[NumFmtExn](data.toInt).leftMap(_ => List(s"$name must be an integer"))
def nonBlank(name: String)(data: String): FailFast[String] =
Right(data).ensure(List(s"$name cannot be blank"))(_.nonEmpty)
def nonNegative(name: String)(data: Int): FailFast[Int] =
Right(data).ensure(List(s"$name must be non-negative"))(_ >= 0)
def readName(data: FormData): FailFast[String] =
getValue("name")(data).
flatMap(nonBlank("name"))
def readAge(data: FormData): FailFast[Int] =
getValue("age")(data).
flatMap(nonBlank("age")).
flatMap(parseInt("age")).
flatMap(nonNegative("age"))
case class User(name: String, age: Int)
type FailSlow[A] = Validated[List[String], A]
import cats.instances.list._ // for Semigroupal
import cats.syntax.apply._ // for mapN
def readUser(data: FormData): FailSlow[User] =
(
readName(data).toValidated,
readAge(data).toValidated
).mapN(User.apply)
Некоторые примечания: каждая примитивная функция проверки: nonBlank
, nonNegative
, getValue
возвращает такназывается типом FailFast, который является монадическим, а не аппликативным.
Есть 2 функции readName
и readAge
, которые используют композицию предыдущих, а также являются FailFast по своей природе.
readUser
наоборот,терпеть неудачу медленноДля достижения этого результаты readName
и readAge
конвертируются в Validated и составляются с помощью так называемого «синтаксиса»
Давайте предположим, что у меня есть еще одна функция для проверки, которая принимает имя и возраст, подтвержденные readName
и readAge
.Для примера:
//fake implementation:
def validBoth(name:String, age:Int):FailSlow[User] =
Validated.valid[List[String], User](User(name,age))
Как сочинить validBoth
с readName
и readAge?С fail fast это довольно просто, потому что я использую for-comrehension
и имею доступ к результатам readName
и readAge
:
for {
n <- readName...
i <- readAge...
t <- validBoth(n,i)
} yield t
, но как получить тот же результат для failslow?
РЕДАКТИРОВАТЬ вероятно, это не достаточно ясно, с этими функциями.Вот реальный пример использования.Существует функция, похожая на readName / readAge, которая аналогичным образом проверяет дату.Я хочу создать функцию проверки, которая принимает 2 даты, чтобы убедиться, что одна дата приходит за другой.Дата происходит от строки.Вот пример того, как это будет выглядеть для FailFast, который не является лучшим вариантом в этом контексте:
def oneAfterAnother(dateBefore:Date, dateAfter:Date): FailFast[Tuple2[Date,Date]] =
Right((dateBefore, dateAfter))
.ensure(List(s"$dateAfter date cannot be before $dateBefore"))(t => t._1.before(t._2))
for {
dateBefore <- readDate...
dateAfter <- readDate...
t <- oneDateAfterAnother(dateBefore,dateAfter)
} yield t
Моя цель - накапливать возможные ошибки с датами аппликативным способом.В книге сказано, с.157:
Мы не можем flatMap, потому что Validated не является монадой.Тем не менее, Cats обеспечивает замену flatMap под названием andThen.Сигнатура типа andThen идентична сигнатуре flatMap, но у нее другое имя, поскольку она не является законной реализацией в отношении законов монады:
32.valid.andThen { a =>
10.valid.map { b =>
a + b
}
}
Хорошо, я пытался использовать повторноэто решение, основанное на andThen
, но результат имел монадический, а не аппликативный эффект:
def oneDateAfterAnotherFailSlow(dateBefore:String, dateAfter:String)
(map: Map[String, String])(format: SimpleDateFormat)
: FailSlow[Tuple2[Date, Date]] =
readDate(dateBefore)(map)(format).toValidated.andThen { before =>
readDate(dateAfter)(map)(format).toValidated.andThen { after =>
oneAfterAnother(before,after).toValidated
}
}