validations in value classes

前端 未结 3 1024
孤独总比滥情好
孤独总比滥情好 2021-01-06 23:09

SIP-15 implies one can use value classes to define for example new numeric classes, such as positive numbers. Is it possible to code such a constraint that the underlying >

相关标签:
3条回答
  • 2021-01-06 23:28

    An implicit conversion to a type marked as having passed your runtime requirement.

    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
    

    You can also have private methods that enforce invariants.

    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
    
    0 讨论(0)
  • 2021-01-06 23:28

    The way I have accomplished this is to use the companion object's .apply method to add a require constraint prior to calling the case class's private constructor "instantiating" the value.

    WARNING: The code below will not compile in the REPL/Scala Worksheet. A case class extending AnyVal must be a top-level class; i.e. cannot be nested within the scope of another class, trait, or object. And both the REPL and Scala Worksheet are implemented by pushing all the code into an invisible containing class before executing.

    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
    
    0 讨论(0)
  • 2021-01-06 23:46

    You could use refined to lift the validation step to compile time by refining your Double with refined's Positive predicate:

    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)
                       ^
    

    Note that the last error is a compile error and not a runtime error.

    0 讨论(0)
提交回复
热议问题