【问题标题】:Scala - simple design by contractScala - 简单的契约设计
【发布时间】:2013-03-04 15:51:05
【问题描述】:

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

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

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

【问题讨论】:

  • 实际上 C 和 C++ 有非负整数:在 google 中查找“unsigned int”。
  • 仅供参考:pm.inf.ethz.ch/education/theses/student_docs/Rokas_Matulis/… 开发的编译器插件适用于 2.10.0,但我们还没有以任何方式发布它(还),因为它 - 作为理学硕士的论文 - 并不是很稳定。
  • @om-nom-nom。是的我同意。自从我做 C/C++ 以来已经太久了,但如果我记得的话,它很容易绕过。无论如何,我的“真正”意图(对不起)是询问有用的数字类型,而不仅仅是 NonNegativeInt。一个例子是DegreesCentigrade,或任何其他特定于域的 - 增加了我喜欢 Pascal 的类型安全性。
  • @mhs:很好。当它成为 Scala 的一部分,或者离开实验室并进入主流使用(维护等)时,我会对使用它非常感兴趣。我对 Scala 的了解越多,我的印象就越深刻:能够混合 DbC 真是太酷了!继续加油!

标签: scala


【解决方案1】:

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

【讨论】:

  • 好的,因此非空契约隐含在惯用的 Scala 中:我可以接受 - 将 Option 用于 Optional。我仍然更喜欢(直到我对我认为的习语变得更加熟悉),能够明确说明非空性。价值等级也看起来像门票。谢谢。因此接受您的回答
【解决方案2】:

你所说的null是什么?

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

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

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

附录

Scala 标准库本身以Option 工厂的形式轻松实现了从可空值到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

【讨论】:

  • 谢谢。我想在我的界面上记录意图,但我完全同意将 null 排除到系统边界。好的!不再有防御性的空检查。但这不是强制执行的。 C++ 引用强制(几乎足够)非空性我还是 Scala 的新手,所以我对很多这些东西都很模糊。忍受 Java 太多年了 :)
  • 怎么不强制执行? Option 值是 NoneSome(value)。类型本身用于记录可能的替代方案。最后,如果nulls 来自一些非 Scala 代码,那么它们会这样做,并且没有任何相反的文档会改变这一点。所以我不清楚你的反对意见是什么。
  • 他是正确的,选项类型不强制非空。除了 None 和 Some(value) 之外,选项类型也可以是 null 或(更糟的) Some(null)。是的,这是一种可怕的风格,但编译器和运行时肯定不会阻止它。
  • 恕我直言,final case class Some(x) 构造函数(在 Option.scala 中)缺少一个重要成分:require(x != null)。如果要添加它,就不可能意外创建Some(null)
  • C++ 的引用形成运算符并不能真正保证结果的非空性,不是吗?你可以强求:int *i = null; int &amp;ir = &amp;(*i)(我的 C++ 很生锈,但我认为这是对的......)。无论如何,除非你愿意编写编译器插件或一些宏,否则我认为你不会在 Scala 中得到你想要的东西。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2011-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多