我应该使用隐式转换来强制执行前置条件吗?

时间:2011-01-08 00:18:47

标签: scala

我发现我可以使用隐式转换来宣布和强制执行前提条件。考虑一下:

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表达一个函数只占用类值的整个范围的子集的概念。

首先是:

  1. 一个好主意,
  2. 一个坏主意,或
  3. 其他人已经知道的明显想法
  4. 其次,这对于基本类型(如Int和String)最有用。当然,这些类是最终的,所以有一种很好的方法,不仅可以在函数中使用受限类型(这是第二个隐含的类型),还可以委托基础值上的所有方法(缺少手动实现每个委托) )?

3 个答案:

答案 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,它有两个子类SignedIntUnsignedInt。然后你可以在UnsignedInt中定义减法,如下所示:

def -(i:Int):MaybeSignedInt

看起来非常糟糕,不是吗?实际上,数字的符号在概念上不应该是数字的类型的属性,而是它的值。