2015-10-14 3 views
2

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 
+1

Вначале базовый должен быть 'val' – maks

+0

, а во-вторых,' require' оператор не соответствует эфемеричности класса значений – maks

ответ

3

Вы можете использовать refined поднять шаг проверки времени компиляции пути уточнения вашего Double с рафинированным-х Positive предиката:

import eu.timepit.refined.auto._ 
import eu.timepit.refined.numeric._ 
import [email protected]@ 

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

scala> Volatility(1.5) 
res1: Volatility = [email protected] 

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

Обратите внимание, что последняя ошибка возникает ошибка компиляции, а не ошибка времени выполнения.

+0

Большое спасибо, это действительно отвечает на мой вопрос. Сохраняет ли это преимущество оболочки AnyVal вокруг примитива, что примитивный экземпляр бокса не создается во время выполнения? –

+1

К сожалению, нет. Использование '@@' здесь приведет к тому, что« Double »будет помещен в коробку (подробнее см. Https://github.com/fthomas/refined#performance-concerns). Но в настоящее время я изучаю, как можно избежать бокса для примитивных типов (https://github.com/fthomas/refined/issues/76). –

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 
+0

Я отредактировал свой первоначальный вопрос, чтобы уточнить, что проверка не должна быть идеальным вызовом метода , –