假设我有几个案例类和很少的函数来测试它们。
case class PersonName(...)
case class Address(...)
case class Phone(...)
def tesPersonName(pn: PersonName): Either[String, PersonName] = ...
def testAddress(a: Address): Either[String, Address] = ...
def testPhone(p: Phone): Either[String, Phone] = ...
现在我定义了一个新的案例类Person
和一个测试函数,快速失败。
case class Person(name: PersonName, address: Address, phone: Phone)
def testPerson(person: Person): Either[String, Person] = for {
pn <- testPersonName(person.name).right
a <- testAddress(person.address).right
p <- testPhone(person.phone).right
} yield person;
现在我希望函数testPerson
能够累积错误而不是快速失败。
我希望函数testPerson
始终执行所有test*
函数并返回Either[List[String], Person]
。我怎么能这样做?
答案 0 :(得分:15)
您希望隔离test*
方法并停止使用理解!
假设(无论出于什么原因)scalaz不适合你......可以在不必添加依赖项的情况下完成。
与许多scalaz示例不同,这是一个库不会比“常规”scala更多地减少冗长的例子:
def testPerson(person: Person): Either[List[String], Person] = {
val name = testPersonName(person.name)
val addr = testAddress(person.address)
val phone = testPhone(person.phone)
val errors = List(name, addr, phone) collect { case Left(err) => err }
if(errors.isEmpty) Right(person) else Left(errors)
}
答案 1 :(得分:13)
Scala的for
- 理解(对于flatMap
和map
的调用的组合)旨在允许您以您可以访问的方式对monadic计算进行排序后续步骤中早期计算的结果。请考虑以下事项:
def parseInt(s: String) = try Right(s.toInt) catch {
case _: Throwable => Left("Not an integer!")
}
def checkNonzero(i: Int) = if (i == 0) Left("Zero!") else Right(i)
def inverse(s: String): Either[String, Double] = for {
i <- parseInt(s).right
v <- checkNonzero(i).right
} yield 1.0 / v
这不会累积错误,事实上也没有合理的方法。假设我们打电话给inverse("foo")
。那么parseInt
显然会失败,这意味着我们无法获得i
的值,这意味着我们无法继续前进到序列中的checkNonzero(i)
步骤。
在您的情况下,您的计算没有这种依赖性,但您正在使用的抽象(monadic排序)并不知道。你想要的是类似Either
的类型,它不是monadic,但是 applicative 。有关差异的详细信息,请参阅my answer here。
例如,您可以使用Scalaz的Validation
编写以下内容,而无需更改任何单独的验证方法:
import scalaz._, syntax.apply._, syntax.std.either._
def testPerson(person: Person): Either[List[String], Person] = (
testPersonName(person.name).validation.toValidationNel |@|
testAddress(person.address).validation.toValidationNel |@|
testPhone(person.phone).validation.toValidationNel
)(Person).leftMap(_.list).toEither
虽然当然这比必要的更冗长,并且丢弃了一些信息,并且在整个过程中使用Validation
会更加清晰。
答案 2 :(得分:4)
正如@TravisBrown告诉你的那样,因为理解并没有真正与错误累积混合。实际上,当您不希望进行细粒度错误控制时,通常会使用它们。
理解是为了在发现的第一个错误上“短路”,这几乎总是你想要的。
您正在做的坏事是使用String
来执行异常的流量控制。您应始终使用Either[Exception, Whatever]
并使用scala.util.control.NoStackTrace
和scala.util.NonFatal
微调记录。
有更好的选择,特别是:
scalaz.EitherT
和scalaz.ValidationNel
。
更新 :(这是不完整的,我不知道你想要什么)。您有比匹配更好的选项,例如getOrElse
和recover
。
def testPerson(person: Person): Person = {
val attempt = Try {
val pn = testPersonName(person.name)
val a = testAddress(person.address)
testPhone(person.phone)
}
attempt match {
case Success(person) => //..
case Failure(exception) => //..
}
}
答案 3 :(得分:0)
从trmm50km_resample = resample(trmm25,trmm50km, method ='bilinear')
class : RasterLayer
dimensions : 200, 720, 144000 (nrow, ncol, ncell)
resolution : 0.5, 0.5 (x, y)
extent : -180, 180, -50, 50 (xmin, xmax, ymin, ymax)
coord. ref. : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
data source : in memory
names : ppt_200204
values : 0, 1.482134 (min, max)
trmm100km_resample = resample(trmm25,trmm100km, method ='bilinear')
class : RasterLayer
dimensions : 100, 360, 36000 (nrow, ncol, ncell)
resolution : 1, 1 (x, y)
extent : -180, 180, -50, 50 (xmin, xmax, ymin, ymax)
coord. ref. : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
data source : in memory
names : ppt_200204
values : 0, 1.202185 (min, max)
values(trmm50km) == values(trmm50km_resample) true
values(trmm100km) == values(trmm100km_resample) false
开始,我们可以partitionMap
个Scala 2.13
中的List
个,以便根据元素Either
的面对元素进行分区。
Either
如果左侧为空,则没有元素// def testName(pn: Name): Either[String, Name] = ???
// def testAddress(a: Address): Either[String, Address] = ???
// def testPhone(p: Phone): Either[String, Phone] = ???
List(testName(Name("name")), testAddress(Address("address")), testPhone(Phone("phone")))
.partitionMap(identity) match {
case (Nil, List(name: Name, address: Address, phone: Phone)) =>
Right(Person(name, address, phone))
case (left, _) =>
Left(left)
}
// Either[List[String], Person] = Left(List("wrong name", "wrong phone"))
// or
// Either[List[String], Person] = Right(Person(Name("name"), Address("address"), Phone("phone")))
,因此我们可以从Left
元素中构建Person
。
否则,我们返回Right
值中的Left
List
。
中间步骤(Left
)的详细信息:
partitionMap