Scala - 合同简单设计

时间:2013-03-04 15:51:06

标签: scala

我正在学习Scala作为个人项目,因为我厌倦了Java的冗长。我喜欢我所看到的很多东西,但想知道是否有办法有效在方法上实现一些简单的合同。我不是(必然)在完整的DbC之后,但有办法: -

  1. 表示参数或类字段是必需的,即不能为空。 Option事件似乎干净地表明是否存在OPTIONAL值,但我想指定类不变量(x是必需的),并且还要简洁地指定需要参数。我知道我可以做“if's”抛出某种异常,但我想要一个非常常见的用例语言功能。我喜欢我的界面,我不喜欢防御性编程。

  2. 是否可以定义简洁有效(运行时性能)范围类型,例如“NonNegativeInt” - 我想说参数是> = 0.或者在一个范围内。 PASCAL有这些类型,我发现它们非常适合传达意图。这是C,C ++,Java等的一大缺点。当我说简洁时,我的意思是我想要像普通的int一样容易地声明这种类型的变量,而不是每个都是新的和堆上的每个实例。

3 个答案:

答案 0 :(得分:7)

对于第(1)点,Option确实应该足够了。这是因为虽然scala支持null值,但它主要是为了与Java兼容。 Scala代码不应该包含null,值,它应该被限制在非常本地化的位置,并尽快转换为选项(好的scala代码永远不会让null值传播)。 因此,在惯用scala中,如果某个字段或参数类型为Option,这实际上意味着它是必需的。

现在,还有(实验性的,据我所知,从未完全支持)NotNull特性。见How does the NotNull trait work in 2.8 and does anyone actually use it?

对于第(2)点,scala 2.10引入了value classes。有了它们,你可以定义你自己的包装Int而没有运行时开销的类,并根据你的需要实现它的运算符。您将进行运行时检查的唯一地方是从正常Int转换为NonNegativeInt(如果int为负,则抛出异常)。请注意,每次创建新NonNegativeInt时都会执行此检查,这也意味着每次执行操作时都会产生非空运行时影响。但Pascal处于相同的情况(范围检查是在运行时在Pascal中执行的)所以我猜你对此没问题。

更新:以下是NonNegativeInt的示例实现(此处已重命名为UInt):

object UInt {
  def apply( i: Int ): UInt = {
    require( i >= 0 )
    new UInt( i )
  }
}
class UInt private ( val i: Int ) extends AnyVal {
  override def toString = i.toString
  def +( other: UInt ) = UInt( i + other.i)
  def -( other: UInt ) = UInt( i - other.i)
  def *( other: UInt ) = UInt( i * other.i)
  def /( other: UInt ) = UInt( i / other.i)
  def <( other: UInt ) = i < other.i
  // ... and so on
}

以及REPL中的一些示例用法:

scala> UInt(123)
res40: UInt = 123

scala> UInt(123) * UInt(2)
res41: UInt = 246

scala> UInt(5) - UInt(8)
java.lang.IllegalArgumentException: requirement failed
        at scala.Predef$.require(Predef.scala:221)
        at UInt$.apply(<console>:15)
        ...

答案 1 :(得分:4)

你说的null是什么?

说真的,在您系统的边界处设置null,它会与您未编写的代码接触。在该边界处,您确保所有可以为空的值都转换为Option

同样,不要使用例外。和null一样,在门口禁止他们。将它们变为Either或使用ScalaZ Validation

对于依赖类型(类型与特定值或自然数值的子集相互作用或依赖于它们),它的工作量更大。但是,Spire具有Natural类型。它可能不是你想要的,因为它是任意精度,但它确实强加了自然数的非负面方面。

<强>附录

Scala标准库本身以Option factroy的形式轻松地从可空值转换为Option。即:

scala>     val s1 = "Stringy goodness"
s1: String = Stringy goodness

scala>     val s2: String = null
s2: String = null

scala>     val os1 = Option(s1)
os1: Option[String] = Some(Stringy goodness)

scala>     val os2 = Option(s2)
os2: Option[String] = None

答案 2 :(得分:0)

Scala标准库内置了以下类型的断言机制:assertassumerequiredensuring方法。后两者特别允许您以“按合同设计”样式编写前置条件和后置条件。自然数除法的简单示例:

def divide(x: Int, y: Int): Int = {
  require(x > y, s"$x > $y")
  require(y > 0, s"$y > 0")

  x / y
} ensuring (_ * y == x)

如果未满足要求,则require调用将引发IllegalArgumentException,并显示插值字符串作为异常的消息。如果给定条件不成立,ensuring调用将引发异常。

更多详细信息,请访问:https://madusudanan.com/blog/scala-tutorials-part-29-design-by-contract/

还有一个工具可以对以以下样式编写的Scala子集进行形式验证:https://github.com/epfl-lara/stainless