对于我正在处理的项目,我想创建一个表示资源数量的类。将来它可能会成为不同资源类型的计数集合(因此不会将Resources类本身编码为Value类),但是现在单个(匿名)资源类型就足够了。
但是,这个资源计数永远不应该是负数,所以我想限制它可以拥有的自然数集(即非负整数)。为此,我正在寻找创建一个名为Nat。
的新价值类我想要实现的几个语义点:
这意味着除了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)
}
}
}
正如我所说,它似乎有效,但是周围的工作感觉有点笨拙。有关改进的建议吗?
答案 0 :(得分:1)
解决方案很简单:从负整数构造Nat
时,也会截断为零。除了更简单之外,解决方案将更加一致。我不明白为什么aNat + -1
与aNat + Nat(-1)
的工作方式不同(包括同时抛出相同异常的情况)。事实上,编程语言本身就是通过强迫你进入一个复杂的,不自然的结构来告诉你这个一致性问题。
如果你真的想在Int
和Nat
s之间做出这种区别,那么不要试图欺骗该语言(以及其他开发人员!)。说实话,为Int
定义一个完全不同的运算符。不只是过载。建议名称:safeAdd
,intAdd
或类似名称。