Scala-契约式简单设计

Scala-契约式简单设计,scala,Scala,我把Scala作为一个个人项目来学习,因为我厌倦了Java的冗长。我喜欢我看到的很多东西,但是想知道是否有一种方法可以有效地在方法上实现一些简单的契约。我(不一定)需要完整的DbC,但有没有办法:- 指示参数或类字段是必需的,即不能为null。选项似乎清楚地指示了是否存在可选值,但我想指定类不变量(x是必需的),并简洁地指定一个参数是必需的。我知道我可以做“如果”抛出某种异常,但我想为这个非常常见的用例提供一个语言特性。我喜欢紧凑的界面,不喜欢防御性编程 是否可以定义简洁高效的(运行时性能)范围

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

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

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

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

    现在,还有一个特性(据我所知,它是实验性的,从未得到过完全支持)
    NotNull
    。看

    对于第(2)点,Scala2.10引入了。有了它们,您可以定义自己的类来包装
    Int
    ,而无需运行时开销,并根据需要实现其运算符。只有在从正常的
    Int
    转换为
    NonNegativeInt
    时,才会进行运行时检查(如果Int为负值,则引发异常)。请注意,每次创建新的
    非负性int
    时都会执行此检查,这也意味着每次执行操作时都会发生非空运行时影响。但是Pascal的情况也是一样的(范围检查是在运行时用Pascal执行的),所以我想您可以接受这一点

    更新:以下是
    非否定项
    (此处重命名为
    UInt
    )的示例实现:

    对象UInt{
    def应用(i:Int):UInt={
    需要(i>=0)
    新界南(一)
    }
    }
    类UInt private(val i:Int)扩展了AnyVal{
    覆盖def toString=i.toString
    def+(其他:UInt)=UInt(i+其他.i)
    def-(其他:UInt)=UInt(i-其他.i)
    def*(其他:UInt)=UInt(i*other.i)
    def/(其他:UInt)=UInt(i/other.i)
    def UInt(123)
    res40:UInt=123
    scala>UInt(123)*UInt(2)
    res41:UInt=246
    scala>UInt(5)-UInt(8)
    java.lang.IllegalArgumentException:需求失败
    在scala.Predef$.require处(Predef.scala:221)
    在UInt$处应用(:15)
    ...
    
    你所说的
    null
    是什么

    说真的,在您的系统边界处,条形码
    null
    ,它与您未编写的代码接触。在该边界处,您确保所有可为null的值都转换为
    选项

    同样,也不要使用异常。与
    null
    一样,在大门处禁止它们。将它们转换为
    或者使用ScalaZ
    验证

    至于依赖类型(类型与特定值或值的子集(如自然数)交互或依赖于特定值或值的子集),它需要更多的工作。但是,它有一个
    natural
    类型。它可能不完全是您想要的,因为它的精度是任意的,但它确实施加了自然数的非负方面

    附录

    Scala标准库本身以
    选项
    factroy的形式提供了从可空值到
    选项
    的转换

    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
    

    Scala标准库内置了这些类型的断言机制:
    断言
    假设
    必需
    、和
    确保
    方法。后两者特别允许您在契约式设计中编写前置条件和后置条件。自然数划分的简单示例:

    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
    ,并将插入的字符串显示为异常消息。如果给定条件不成立,则
    确保
    调用抛出一个异常

    详情请浏览:


    还有一种工具,它对Scala的一个子集进行了形式化验证:

    实际上C和C++在GooGeL.FYI中有非负int:查找“un签署int”:开发的编译器插件工作2.2.0,但我们还没有以任何方式发布它(还没有)因为它——作为理学硕士的论文——不是很稳定。@om-nom-nom。是的,我同意。我已经很久没有学过C/C++了,但是如果我回忆起来的话,它很容易规避。总之,我的“真正”意图(对不起)是询问有用的数字类型,而不仅仅是非负性。例如,
    DegreesCentigrade
    ,或任何其他特定于域的-我喜欢在Pascal中增加的类型安全性。@mhs:Nice。当它成为Scala的一部分,或移出实验室并进入主流使用时(维护等)然后我会对使用它非常感兴趣。我对Scala的了解越多,我就越印象深刻:能够混入DbC真是太酷了!保持下去!谢谢。我想记录我的界面意图,但我完全同意
    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
    
    def divide(x: Int, y: Int): Int = {
      require(x > y, s"$x > $y")
      require(y > 0, s"$y > 0")
    
      x / y
    } ensuring (_ * y == x)