首先,我是Scala的新手,没有任何编写生产代码的经验,因此我缺乏对社区中被认为是最佳/最佳实践的理解。我偶然发现了这些资源:
那里提到抛出异常不是一种很好的习惯,这使我认为那时定义函数前提条件的好方法是什么,因为
抛出一个函数有点谎言:它的类型暗示了它不存在时的全部功能。
经过一些研究,似乎使用Option
/ Either
/ Try
/ Or
(标量)是一种更好的方法,因为您可以使用类似T Or IllegalArgumentException
作为返回类型可以清楚地表明该函数实际上是部分函数,它使用异常作为一种存储可以包装在其他异常中的消息的方式。
但是缺乏Scala经验,我不太了解这对于实际项目是否可行,或者使用Predef.require
是可行的方法。如果有人解释了Scala社区通常的工作方式以及原因,我将不胜感激。
我也见过Functional assertion in Scala,但是虽然这个主意看起来很有趣,但我认为PartialFunction
并不十分适合此目的,因为通常会传递多个参数和元组在这种情况下看起来像黑客。
答案 0 :(得分:6)
Option
或Either
绝对是进行函数式编程的方法。
使用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.IO
或monix.reactive.Observable
本身包含错误处理。
我还建议研究cats.data.EitherT
以进行基于类型的错误处理。阅读文档,绝对值得。
作为一个附带说明,对于来自Java的每个人,Scala都将所有Exception
视为Java对待RuntimeException
。这意味着,即使您的一个依赖项中的不安全代码抛出了(已检查的)IOException
,Scala也将永远不会要求您catch
或以其他方式处理异常。因此,根据经验,使用Java依赖项时,几乎总是将它们包装在Try
中(如果它们执行副作用或阻塞线程,则将它们包装在IO
中。)
答案 1 :(得分:5)
我认为您的推理是正确的。如果您有一个简单的总计(部分函数的对立)函数,且参数可能具有无效类型,那么最常见,最简单的解决方案是返回一些可选结果,例如Option
等。>
通常不建议抛出异常,因为它们会违反FP法律。如果您需要以Option
尴尬的方式编写结果,则可以使用任何比Validation
返回更高级类型的库,例如Scalaz Option
。
我可以提供的另外两种选择是使用:
val i: Int Refined Positive = 5
。您也可以编写自己的类型,这些类型可以包装原始类型并声明一些属性。这里的问题是,如果您的参数具有多个相互依赖的有效值,则每个参数互斥。例如x > 1 and y < 1
或x < 1 and y > 1
。在这种情况下,您可以返回一个可选值,而不是使用这种方法。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)
您可以使用函数的返回类型来指示结果的类型。
如果您要描述一个由于某种原因而可能失败的函数,那么您提到的类型可能会返回Try
或Either
:我将“尝试”给出一个结果,或者将返回“成功”或“失败”的结果。
现在,您可以指定自定义例外
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
解决方案清楚地表明您的函数可能返回的错误