当函数具有参数前提条件时,返回Either / Option / Try / Or是否被视为可行/惯用的方法?

时间:2019-06-27 07:20:30

标签: scala error-handling

首先,我是Scala的新手,没有任何编写生产代码的经验,因此我缺乏对社区中被认为是最佳/最佳实践的理解。我偶然发现了这些资源:

  1. https://github.com/alexandru/scala-best-practices
  2. https://nrinaudo.github.io/scala-best-practices/

那里提到抛出异常不是一种很好的习惯,这使我认为那时定义函数前提条件的好方法是什么,因为

  

抛出一个函数有点谎言:它的类型暗示了它不存在时的全部功能。

经过一些研究,似乎使用Option / Either / Try / Or(标量)是一种更好的方法,因为您可以使用类似T Or IllegalArgumentException作为返回类型可以清楚地表明该函数实际上是部分函数,​​它使用异常作为一种存储可以包装在其他异常中的消息的方式。

但是缺乏Scala经验,我不太了解这对于实际项目是否可行,或者使用Predef.require是可行的方法。如果有人解释了Scala社区通常的工作方式以及原因,我将不胜感激。

我也见过Functional assertion in Scala,但是虽然这个主意看起来很有趣,但我认为PartialFunction并不十分适合此目的,因为通常会传递多个参数和元组在这种情况下看起来像黑客。

3 个答案:

答案 0 :(得分:6)

OptionEither绝对是进行函数式编程的方法。

使用Option记录为什么 None很重要。

对于Either,左侧是不成功的值(“错误”),而右侧是成功的值。左侧不一定是Exception(或其子类型),它可以是简单的错误消息String(类型别名是您的朋友),也可以是适合您的应用程序。

作为示例,在使用Either处理错误时,我通常使用以下模式:

// Somewhere in a package.scala
type Error = String // Or choose something more advanced
type EitherE[T] = Either[Error, T]

// Somewhere in the program
def fooMaybe(...): EitherE[Foo] = ...

Try仅应用于包装不安全(大多数情况下,纯Java)代码,使您能够对结果进行模式匹配:

Try(fooDangerous()) match {
   case Success(value) => ...
   case Failure(value) => ...
}

但是我建议仅在本地使用Try,然后再从那里使用上述数据类型。

一些高级数据类型,例如cats.effect.IOmonix.reactive.Observable本身包含错误处理。

我还建议研究cats.data.EitherT以进行基于类型的错误处理。阅读文档,绝对值得。


作为一个附带说明,对于来自Java的每个人,Scala都将所有Exception视为Java对待RuntimeException。这意味着,即使您的一个依赖项中的不安全代码抛出了(已检查的)IOException,Scala也将永远不会要求您catch或以其他方式处理异常。因此,根据经验,使用Java依赖项时,几乎总是将它们包装在Try中(如果它们执行副作用或阻塞线程,则将它们包装在IO中。)

答案 1 :(得分:5)

我认为您的推理是正确的。如果您有一个简单的总计(部分函数的对立)函数,且参数可能具有无效类型,那么最常见,最简单的解决方案是返回一些可选结果,例如Option等。

通常不建议抛出异常,因为它们会违反FP法律。如果您需要以Option尴尬的方式编写结果,则可以使用任何比Validation返回更高级类型的库,例如Scalaz Option

我可以提供的另外两种选择是使用:

  1. 类型强制执行先决条件的受约束参数。示例:基于https://github.com/fthomas/refinedval i: Int Refined Positive = 5。您也可以编写自己的类型,这些类型可以包装原始类型并声明一些属性。这里的问题是,如果您的参数具有多个相互依赖的有效值,则每个参数互斥。例如x > 1 and y < 1x < 1 and y > 1。在这种情况下,您可以返回一个可选值,而不是使用这种方法。
  2. 部分函数,​​其本质上类似于可选的返回类型:case i: Int if i > 0 => ...。文件:https://www.scala-lang.org/api/2.12.1/scala/PartialFunction.html

例如: PF的def lift: (A) ⇒ Option[B]将PF转换为您的常规函数​​。

  

将此部分函数转换为返回Option的普通函数   结果。

类似于返回选项。部分功能使用起来有些笨拙,并且不完全与FP友好。

我认为Predef.require属于非常罕见的情况,在这种情况下,您不想允许构造任何无效数据,并且更多地是在这种情况下停止一切。例如,您获得了原本不应获得的参数。

答案 2 :(得分:2)

您可以使用函数的返回类型来指示结果的类型。
如果您要描述一个由于某种原因而可能失败的函数,那么您提到的类型可能会返回TryEither:我将“尝试”给出一个结果,或者将返回“成功”或“失败”的结果。
现在,您可以指定自定义例外

case class ConditionException(message: String) extends RuntimeException(message)

如果您的条件不满足,您将返回,例如

import scala.util._
def myfunction(a: String, minLength: Int): Try[String] = {
  if(a.size < minLength) {
    Failure(ConditionException(s"string $a is too short")
  } else {
    Success(a)
  }
}

通过Either,您将得到

import scala.util._
def myfunction(a: String, minLength: Int): Either[ConditionException,String] = {
  if(a.size < minLength) {
    Left(ConditionException(s"string $a is too short")
  } else {
    Right(a)
  }
}

不是Either解决方案清楚地表明您的函数可能返回的错误