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

发布于 2024-10-11 13:50:31 字数 885 浏览 2 评论 0原文

我突然想到我可以使用隐式转换来声明和强制执行先决条件。考虑一下:

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. 一个其他人都已经知道的明显想法。

其次,这对于基本类型(例如 Int 和 String)最有用。当然,这些类是最终的,所以有没有一种好方法不仅可以在函数中使用受限类型(这就是第二个隐式的用途),而且还可以委托给基础值上的所有方法(无需手动实现每个委托) )?

It occurs to me that I could use use implicit conversions to both announce and enforce preconditions. Consider this:

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);
  }
}

Ignore for the moment the actual vacuity of the example: my point is, the subclass NonNegativeDouble expresses the notion that a function only takes a subset of the entire range of the class's values.

First is this:

  1. A good idea,
  2. a bad idea, or
  3. an obvious idea everybody else already knows about

Second, this would be most useful with basic types, like Int and String. Those classes are final, of course, so is there a good way to not only use the restricted type in functions (that's what the second implicit is for) but also delegate to all methods on the underlying value (short of hand-implementing every delegation)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

夜光 2024-10-18 13:50:31

这是一个非常酷的想法,但不幸的是它的真正潜力无法在 Scala 的类型系统中实现。这里你真正想要的是依赖类型,它允许你对调用者施加证明义务方法来验证参数是否在范围内,这样甚至无法使用无效参数调用该方法

但是,如果没有依赖类型和在编译时验证规范的能力,我认为即使不考虑性能考虑,这也有值得怀疑的价值。考虑一下,它比使用 require 更好吗? 函数来声明方法所需的初始条件,如下所示:

def foo(i:Int) = {
    require (i >= 0)
    i * 9 + 4
}

在这两种情况下,负值都会导致在运行时抛出异常,无论是在 require 中函数或构造 NonNegativeDouble 时。这两种技术都清楚地说明了方法的契约,但我认为构建所有这些专门类型的开销很大,这些类型的唯一目的是封装要在运行时断言的特定表达式。例如,如果您想强制执行稍微不同的前提条件该怎么办?比如说,i > 45?您会专门为该方法构建一个 IntGreaterThan45 类型吗?

我能看到的构建例如 NonNegativeFoo 类型的唯一论点是,如果您有许多仅使用和返回正数的方法。即便如此,我认为回报还是值得怀疑的。

顺便说一句,这类似于问题 How far to go with a强类型语言?,对此我给出了类似的答案。

This is an extremely cool idea, but unfortunately its true potential can't be realized in Scala's type system. What you really want here is dependent types, which allow you to impose a proof obligation on the caller of your method to verify that the argument is in range, such that the method can't even be invoked with an invalid argument.

But without dependent types and the ability to verify specifications at compile-time, I think this has questionable value, even leaving aside performance considerations. Consider, how is it any better than using the require function to state the initial conditions required by your method, like so:

def foo(i:Int) = {
    require (i >= 0)
    i * 9 + 4
}

In both cases, a negative value will cause an exception to be thrown at runtime, either in the require function or when constructing your NonNegativeDouble. Both techniques state the contract of the method clearly, but I would argue that there is a large overhead in building all these specialized types whose only purpose is to encapsulate a particular expression to be asserted at runtime. For instance, what if you wanted to enforce a slightly different precondition; say, that i > 45? Will you build an IntGreaterThan45 type just for that method?

The only argument I can see for building e.g. a NonNegativeFoo type is if you have many methods which consume and return positive numbers only. Even then, I think the payoff is dubious.

Incidentally, this is similar to the question How far to go with a strongly typed language?, to which I gave a similar answer.

幸福%小乖 2024-10-18 13:50:31

实际上这是一个很好的想法,尽管我不会在任何性能敏感的循环中使用它。

@specialization 也可以在这里提供相当多的帮助,帮助提高代码效率......

Quite a neat idea actually, though I wouldn't use it in any performance sensitive loops.

@specialisation could also help out by a fair amount here to help make the code more efficient...

绝不服输 2024-10-18 13:50:31

这在 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

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

This would usually be called "unsigned int" in C. I don't think it's very useful, because you wouldn't be able to define operators properly. Consider this:

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!

Therefore, how would you define the minus operator? Like this maybe:

def -(i:Int):Either[UnsignedInt,Int]

That would make arithmetics with UnsignedInt practically unusable.

Or you define a superclass, MaybeSignedInt, that has two subclasses, SignedInt and UnsignedInt. Then you could define subtraction in UnsignedInt like this:

def -(i:Int):MaybeSignedInt

Seems totally awful, doesn't it? Actually, the sign of the number should not conceptually be a property of the number's type, but of it's value.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文