如何对Scala方法的值强制执行编译时限制?

wdb*_*wdb 7 scala constraints compile-time

我想在编译时强制约束Scala方法的参数值.

例如:

case class Foo(numberOfFoo: Int, ...)
Run Code Online (Sandbox Code Playgroud)

numberOfFooInt上面的,但我真的想把它变成一个正整数.我已经尝试过像PositiveInt这样的类来强制执行此操作,但是这只是将检查推送到另一个仍未进行编译时检查的类.

使用上面的例子,我想要这个:

val n: Int = ...
val f: Foo = Foo(n)
Run Code Online (Sandbox Code Playgroud)

如果n > 0编译而不编译if n <= 0.我不希望实例化代码必须处理可能的异常,处理Option[Foo]或最终使用Foowhere Foo.numberOfFoo != n(即我不想使用输入参数的绝对值).

更新:感谢您提供有用的信息.这是我所担心的.大多数情况下,我希望能够指定必须具有正整数大小的东西的大小.所以这似乎是最好的方法:

case class Foo(bar: Bar) {val n = bar size}
Run Code Online (Sandbox Code Playgroud)

whe*_*ies 8

你将不得不使用精炼的库.这是没有诉诸Nat或其他类型技巧的唯一方法.从README中的示例:

import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._

// This refines Int with the Positive predicate and checks via an
// implicit macro that the assigned value satisfies it:
scala> val i1: Int Refined Positive = 5
i1: Int Refined Positive = 5

// If the value does not satisfy the predicate, we get a meaningful
// compile error:
scala> val i2: Int Refined Positive = -5
<console>:22: error: Predicate failed: (-5 > 0).
       val i2: Int Refined Positive = -5
Run Code Online (Sandbox Code Playgroud)


fla*_*ian 2

另一种方法是使用shapeless库并使用Nat. 限制是您基本上需要Foo在编译时使用已知常量实例化这些实体。

import shapeless.ops.nat_
import shapeless.nat._

case class Foo[T <: Nat](numberOfFoo: Int)(implicit ev: GT[T, _0]

object Foo {
  // The problem is this won't work.
  def apply[T <: Nat](n: Int): Foo[T] = Foo(Nat(n))
}
Run Code Online (Sandbox Code Playgroud)

仅当像这样使用时才有效:

Foo(_1)
Run Code Online (Sandbox Code Playgroud)

从哪里来_1shapeless.nat._。如果您深入研究实现,0即使您无意,该部分也会以无形形式强制执行:

 if (n < 0) c.abort(c.enclosingPosition, s"A Nat cannot represent $n")
Run Code Online (Sandbox Code Playgroud)

坚持简单的事情

然而,这相当麻烦,因为无论您采用哪种方法,它都将依赖于宏,而除非在编译时知道该值,否则宏无法真正起作用。如果最简单的委托方法不再有效,这可能会变得非常有限。

在实践中,绕过这种方法并使用正常的方式可能会更有效。无论您使用无形状库还是上面提到的精炼库,情况都不会改变,因此对于正常用例来说,进行运行时验证可能会更干净。