我正在学习Scala作为个人项目,因为我厌倦了Java的冗长。我喜欢我所看到的很多东西,但想知道是否有办法有效在方法上实现一些简单的合同。我不是(必然)在完整的DbC之后,但有办法: -
表示参数或类字段是必需的,即不能为空。 Option事件似乎干净地表明是否存在OPTIONAL值,但我想指定类不变量(x是必需的),并且还要简洁地指定需要参数。我知道我可以做“if's”抛出某种异常,但我想要一个非常常见的用例语言功能。我喜欢我的界面,我不喜欢防御性编程。
是否可以定义简洁有效(运行时性能)范围类型,例如“NonNegativeInt” - 我想说参数是> = 0.或者在一个范围内。 PASCAL有这些类型,我发现它们非常适合传达意图。这是C,C ++,Java等的一大缺点。当我说简洁时,我的意思是我想要像普通的int一样容易地声明这种类型的变量,而不是每个都是新的和堆上的每个实例。
答案 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标准库内置了以下类型的断言机制:assert
,assume
,required
和ensuring
方法。后两者特别允许您以“按合同设计”样式编写前置条件和后置条件。自然数除法的简单示例:
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