我发现我可以使用隐式转换来宣布和强制执行前提条件。考虑一下:
object NonNegativeDouble {
implicit def int2nnd(d : Double) : NonNegativeDouble = new NonNegativeDouble(d)
implicit def nnd2int(d : NonNegativeDouble) : Double = d.v
def sqrt(n : NonNegativeDouble) : NonNegativeDouble = scala.math.sqrt(n)
}
class NonNegativeDouble(val v : Double ) {
if (v < 0) {
throw new IllegalArgumentException("negative value")
}
}
object Test {
def t1 = {
val d : Double = NonNegativeDouble.sqrt(3.0);
printf("%f\n", d);
val n : Double = NonNegativeDouble.sqrt(-3.0);
}
}
暂时忽略示例的实际空白:我的观点是,子类NonNegativeDouble表达一个函数只占用类值的整个范围的子集的概念。
首先是:
其次,这对于基本类型(如Int和String)最有用。当然,这些类是最终的,所以有一种很好的方法,不仅可以在函数中使用受限类型(这是第二个隐含的类型),还可以委托基础值上的所有方法(缺少手动实现每个委托) )?
答案 0 :(得分:9)
这是一个非常酷的想法,但遗憾的是它的真正潜力无法在Scala的类型系统中实现。你真正想要的是dependent types,它允许你对方法的调用者强加一个证明义务,以验证参数是否在范围内,这样甚至不能调用该方法参数无效。
但是如果没有依赖类型和在编译时验证规范的能力,我认为这具有可疑的价值,即使不考虑性能考虑因素。考虑一下,如何使用require
函数来说明方法所需的初始条件更好,如下所示:
def foo(i:Int) = {
require (i >= 0)
i * 9 + 4
}
在这两种情况下,负值都会导致在require
函数或构建NonNegativeDouble
时在运行时抛出异常。这两种技术都清楚地说明了方法的契约,但我认为构建所有这些特殊类型的开销很大,其唯一目的是封装在运行时声明的特定表达式。例如,如果您想强制执行稍微不同的前提条件,该怎么办?说,i > 45
?您是否会为该方法构建IntGreaterThan45
类型?
我可以看到建立的唯一论据,例如NonNegativeFoo
类型是指如果您有许多消耗和返回正数的方法。即便如此,我认为收益是可疑的。
顺便提一下,这与问题How far to go with a strongly typed language?类似,我给出了类似的答案。
答案 1 :(得分:0)
实际上有一个很好的想法,虽然我不会在任何性能敏感的循环中使用它。
@specialisation也可以帮助提高代码的效率......
答案 2 :(得分:0)
这通常在C中称为“unsigned int”。我认为它不是很有用,因为你无法正确定义运算符。考虑一下:
val a = UnsignedInt(5)
val b = a - 3 // now, b should be an UnsignedInt(2)
val c = b - 3 // now, c must be an Int, because it's negative!
因此,您如何定义减号运算符?像这样可能:
def -(i:Int):Either[UnsignedInt,Int]
这会使UnsignedInt
的算术几乎无法使用。
或者您定义了一个超类MaybeSignedInt
,它有两个子类SignedInt
和UnsignedInt
。然后你可以在UnsignedInt
中定义减法,如下所示:
def -(i:Int):MaybeSignedInt
看起来非常糟糕,不是吗?实际上,数字的符号在概念上不应该是数字的类型的属性,而是它的值。