值类中的验证

时间:2015-10-14 22:17:14

标签: scala value-class

SIP-15意味着可以使用值类来定义新的数字类,例如正数。是否有可能编码这样的约束,即基础>在没有构造函数的情况下0,而不必调用单独的方法来验证约束(即;创建此类的有效实例是succint)?

如果值类具有构造函数的概念,那么可以在下面进行此类验证,但不支持(即下面的代码不会编译)

implicit class Volatility(val underlying: Double) extends AnyVal {
  require(!underlying.isNaN && !underlying.isInfinite && underlying > 0, "volatility must be a positive finite number")
  override def toString = s"Volatility($underlying)"
}

Volatility(-1.0) //should ideally fail

4 个答案:

答案 0 :(得分:3)

隐式转换为标记为已超出运行时要求的类型。

scala> trait Pos
defined trait Pos

scala> implicit class P(val i: Int with Pos) extends AnyVal { def f = i }
defined class P

scala> implicit def cv(i: Int): Int with Pos = { require(i>0); i.asInstanceOf[Int with Pos] }
warning: there was one feature warning; re-run with -feature for details
cv: (i: Int)Int with Pos

scala> new P(42).f
res0: Int with Pos = 42

scala> :javap -prv -
        17: invokevirtual #35                 // Method $line5/$read$$iw$$iw$.cv:(I)I
        20: invokevirtual #38                 // Method $line4/$read$$iw$$iw$P$.f$extension:(I)I

scala> new P(-42).f
java.lang.IllegalArgumentException: requirement failed
  at scala.Predef$.require(Predef.scala:207)
  at .cv(<console>:13)
  ... 33 elided

您还可以使用强制不变量的私有方法。

scala> implicit class P(val i: Int with Pos) extends AnyVal { private def g = require(i>0) ; def f = { g; i } }
defined class P

scala> new P(-42.asInstanceOf[Int with Pos]).f
java.lang.IllegalArgumentException: requirement failed
  at scala.Predef$.require(Predef.scala:207)
  at P$.$line10$$read$P$$g$extension(<console>:14)
  at P$.f$extension(<console>)
  ... 33 elided

答案 1 :(得分:3)

您可以使用refined通过使用精炼的Double谓词优化Positive来提升验证步骤以编译时间:

import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._
import shapeless.tag.@@

scala> implicit class Volatility(val underlying: Double @@ Positive) extends AnyVal
defined class Volatility

scala> Volatility(1.5)
res1: Volatility = Volatility@3ff80000

scala> Volatility(-1.5)
<console>:52: error: Predicate failed: (-1.5 > 0).
       Volatility(-1.5)
                   ^

请注意,最后一个错误是编译错误,而不是运行时错误。

答案 2 :(得分:0)

我完成此操作的方法是在调用案例类的.apply构造函数“实例化”该值之前,使用伴随对象的require方法添加一个private约束。

警告:以下代码不会在REPL / Scala工作表中编译。扩展AnyVal的案例类必须是顶级类。即不能嵌套在另一个类,特征或对象的范围内。 REPL和Scala工作表都是通过在执行之前将所有代码推送到一个不可见的包含类中来实现的。

object PositiveInt {
  def apply(value: Int): PositiveInt = {
    require(value >= 0, s"value [$value] must be greater than or equal to 0")
    new PositiveInt(value)
  }
}
case class PositiveInt private(value: Int) extends AnyVal

val positiveTestA = PositiveInt(0)
val positiveTestB = PositiveInt(1)
val positiveTestC = PositiveInt(-1) //throws required exception

答案 3 :(得分:0)

这对您的用例有用吗?将构造函数设为私有并使用带有验证逻辑的伴随对象来创建新实例。

class User private (val userIdentifier:String) extends AnyVal {}

object User {
  def apply(userIdentifier: String): User = {
    if(Option(userIdentifier).exists(_.trim.isEmpty)) throw new IllegalArgumentException("User identifier cannot be empty!")
    new User(userIdentifier)
  }
}