我有以下功能:
private def constraintToJson(req: => Request[IO])
: EitherT[IO, Throwable, Unit]
= {
val err: EitherT[IO, Throwable, Unit] = EitherT.fromEither[IO](Left(new Exception("Not JSON format request.")))
req.contentType match {
case Some(s) =>
if (s != `Content-Type`(MediaType.`application/json`))
err
else
EitherT.fromEither[IO](Right(()))
case None =>
err
}
}
问题是,如果Unit
是正确的还是有其他选择,那么返回它是错误的?
答案 0 :(得分:3)
我认为返回Either
(包装成EitherT
/ constraintToJson
)可能没问题,如果这是计算的最后一步,它实际上不会产生任何输出。在其他情况下(最有可能包括你的)你应该为成功案例返回一些值,以便你可以进一步链接它。所以主要的问题是:如何使用Request
?如果Content-Type
与JSON匹配,则明显怀疑您的情况是返回SfPicker
或其正文,因为这很可能是下一步将使用的数据。
答案 1 :(得分:2)
让我首先删除IO
来解决您的问题。
问题是具有如下签名的方法是否有用:
def validate[A](a: Request[A]): Either[Error,()] =
if(isOk(a.someProperty)) Left(()) else Right("invalid")
注意到Either[A,()] =:= Option[A]
,此方法检查请求值中是否存在任何Error
,如果检测到,则返回此Error
。
我们可以编写一个检查Request
:
val someRequest: Request[X] = getRequestFrom("somewhere")
validate(someRequest).toLeft.foreach{error =>
println("invalid request: " + error
System.exit(-1)
}
System.exit(0)
在这种情况下,validate
有一个有用的签名。
我们还可以validate
一个请求,然后继续执行它:
val someRequest: Request[X] = getRequestFrom("somewhere")
validate(someRequest) match {
case Left(error) =>
println("invalid request: " + error)
System.exit(-1)
case Right(_) =>
println("executing request: " + someRequest.execute)
System.exit(0)
}
虽然此代码运行良好,但它会在第二个_
子句中抛弃一个参数(match
)。这是一些糟糕设计的指标。 case Right(_)
中的代码从外部获取值someRequest
,并将其视为有效请求,因为您编写代码的方式。
如果我们在someReuqest.execute
案例中调用Left
,我们可能会在执行无效Request
时遇到运行时异常。
根据我们所需的严格程度,这可能是代码味道。
我们可以通过以下方式改进代码。
我们可以通过简单地返回checked参数来规避返回Unit
。
这看起来有点多余,我们稍后会看到如何将返回的值转换为有用的东西。
但我们先来看看这个选项。
def validateTyped[A](a: Request[A]): Either[Error,Request[A]] =
if(isOk(a.someProperty)) Left(a) else Right("invalid")
然后我们可以将检查并继续代码编写为
val someRequest: Request[X] = getRequestFrom("somewhere")
validate(someRequestTyped) match {
case Left(error) =>
println("invalid request: " + error)
System.exit(-1)
case Right(validatedRequest) =>
//we now execute the validated request
println("executing request: " + validatedRequest.execute)
System.exit(0)
}
现在这有点改进了代码。
我们不会在第二个match
子句中丢弃返回的值。
我们可以执行validatedRequest
,因为我们已在validateRequest
的文档中读到返回的Right
值格式正确,并且可以正确执行。
看起来不错吧? 但我们可以做得更好。
我们仍然可以通过更改Request
类型完全阻止执行格式错误的Request
来进一步改进。
case class Request[A](query: String){
def validate: Either[Error,ValidRequest[A]] = ???
}
case class ValidRequest[A](query: String){
def execute: A
}
使用此代码,无法再调用Request.execute
,因为验证现在变为强制性的。
否则代码将无法编译。
我们还注意到现在需要Right
返回的Request.validate
值。
它已成为获得ValidRequest
的唯一来源。
现在无法呼叫execute
未经验证的Request
,但是:
val someRequest: Request[X] = getRequestFrom("somewhere")
someRequest.validate match {
case Left(error) =>
println("invalid request: " + error)
System.exit(-1)
case Right(validatedRequest) =>
println("executing request: " + validatedRequest.execute)
System.exit(0)
}
通过这种方式,我们变成了一个奇怪的方法,将Unit
返回到一个返回有意义且需要的值的方法中。
此外,execute
Request
无法成功验证。