7

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

  1. 表示参数或类字段是必需的,即不能为空。如果存在 OPTIONAL 值,Option 的东西似乎清楚地表明了,但我想指定类不变量(需要 x),并且还要简洁地指定需要一个参数。我知道我可以做“如果”抛出某种异常,但我想要这个非常常见的用例的语言功能。我喜欢我的接口紧凑,我不喜欢防御性编程。

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

4

3 回答 3

8

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

现在,还有(实验性的,据我所知从未完全支持)NotNull特征。请参阅NotNull 特征在 2.8 中如何工作以及是否有人实际使用它?

对于第 (2) 点,scala 2.10 引入了值类。使用它们,您可以定义自己的包装类Int而无需运行时开销,并按照您认为合适的方式实现其运算符。唯一可以进行运行时检查的地方是从正常转换Int为您的NonNegativeInt(如果 int 为负数则抛出异常)。请注意,每次创建 new 时都会执行此检查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)
        ...
于 2013-03-04T16:01:09.000 回答
4

你说的是null什么?

说真的,在你的系统边界设置栏null,在那里它会接触到你没有编写的代码。在该边界处,您确保所有可为空的值都转换为Option.

同样,不要使用异常。和 一样null,把他们拦在门口。将它们变成Either或使用 ScalaZ Validation

至于依赖类型(类型与特定值或值的子集(例如自然数)交互或依赖于该类型),则工作量更大。但是,Spire有一个Natural类型。它可能不是您想要的,因为它是任意精度,但它确实强加了自然数的非负方面。

附录

OptionScala 标准库本身以工厂的形式轻松地实现了从可为空值到的转换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
于 2013-03-04T16:02:35.067 回答
2

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。如果给定条件不成立,调用将引发异常IllegalArgumentExceptionensuring

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

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

于 2019-02-10T04:37:42.557 回答