如何在自然数值类上启用我想要的语义?

时间:2013-08-16 05:57:29

标签: scala

对于我正在处理的项目,我想创建一个表示资源数量的类。将来它可能会成为不同资源类型的计数集合(因此不会将Resources类本身编码为Value类),但是现在单个(匿名)资源类型就足够了。

但是,这个资源计数永远不应该是负数,所以我想限制它可以拥有的自然数集(即非负整数)。为此,我正在寻找创建一个名为Nat。

的新价值类

我想要实现的几个语义点:

  • 如果您尝试从负值创建Nat,则应该抛出异常。
  • 如果你试图将Int(或另一个Nat)添加到现有的Nat,它应该可以工作,如果传入的int是一个足够大的负数,则将值截断为零 - 没有抛出异常!

这意味着除了def +(nat: Nat)之外,我还需要某种形式的def +(int: Int),否则传递给+的Int将首先转换为Nat,这可能会导致异常。但是因为Nat是一个Value类,所以这两种方法在擦除后会有相同的签名,所以这样就行了。

我也尝试过def +(int: RichInt),希望隐式转换优先,但RichInt也是一个Value类,因此会出现同样的问题。

我发现的一个解决方法是使用混合到RichInt中的一个特征,特别是OrderedProxy。现在,Int将被隐式转换为RichInt并作为OrderedProxy传递给此方法(其形式不会被识别为Value类),而不是转换为Nat,我得到了我想要的语义。

因此,到目前为止,我的代码如下所示:

import runtime.{IntegralProxy, OrderedProxy}

class Nat private(val self: Int) extends AnyVal with IntegralProxy[Int]
{
  protected def num = scala.math.Numeric.IntIsIntegral
  protected def ord = scala.math.Ordering.Int

  import Nat._
  def isZero = (this == Zero)
  def +(nat: Nat): Nat = Nat(self + nat.self)
  def +(int: OrderedProxy[Int]): Nat = trunc(self + int.self)
  def -(nat: Nat): Nat = trunc(self - nat.self)
  def -(int: OrderedProxy[Int]): Nat = trunc(self - int.self)
  def -%(nat: Nat) = (this - nat).self match { // Create a tuple with the reduced count of the minuend, plus any remainder from the subtrahend if the minuend is now zero.
    case 0 => (Zero, (nat - this))
    case nonZero => (Nat(nonZero), Zero)
  }
}

object Nat
{
  val NEG_PARAM_MSG = "Cannot assign negative value"

  val Zero: Nat = Nat(0)

  def apply(value: Int): Nat = value match {
      case cnt if (cnt < 0) => throw new RuntimeException(NEG_PARAM_MSG)
      case 0 => Zero
      case cnt => new Nat(cnt)
    }

  def apply(value: Long): Nat = apply(value.toInt)

  def trunc(value: Int): Nat = value match {
      case cnt if (cnt <= 0) => Zero
      case cnt => new Nat(cnt)
    }

  def trunc(value: Long): Nat = trunc(value.toInt)
}

trait ResourcesComponent
{
  import Nat._

  sealed case class Resources(count: Nat)
  {
    import Resources._

    require(count != Zero || hasNone)

    def hasNone = (this == none)
    def +(res: Resources) = Resources(count + res.count)
    def -(res: Resources) = Resources(count - res.count)
    def -%(res: Resources) = (count - res.count).self match { // Similar to -% for Nat, but convert to a tuple of Resources - is there a better (eg. '.map'-like) way to do this?
      case 0 => (none, Resources(res.count - count))
      case leftOver => (Resources(leftOver), none)
    }
  }

  object Resources
  {
    val NEG_RES_MSG = "Cannot assign negative resources"

    def apply(value: OrderedProxy[Int]) = value.self match {
        case cnt if (cnt < 0) => throw new RuntimeException(NEG_RES_MSG)
        case 0 => none
        case cnt => new Resources(Nat(cnt))
      }

    object none extends Resources(Zero)
    {
      override def hasNone = true
      override def +(res: Resources) = res
      override def -(res: Resources) = none
      override def -%(res: Resources) = (none, res)
    }
  }
}

正如我所说,它似乎有效,但是周围的工作感觉有点笨拙。有关改进的建议吗?

1 个答案:

答案 0 :(得分:1)

解决方案很简单:从负整数构造Nat时,也会截断为零。除了更简单之外,解决方案将更加一致。我不明白为什么aNat + -1aNat + Nat(-1)的工作方式不同(包括同时抛出相同异常的情况)。事实上,编程语言本身就是通过强迫你进入一个复杂的,不自然的结构来告诉你这个一致性问题。

如果你真的想在IntNat s之间做出这种区别,那么不要试图欺骗该语言(以及其他开发人员!)。说实话,为Int定义一个完全不同的运算符。不只是过载。建议名称:safeAddintAdd或类似名称。