假设我有一个名为Rational
的类,它“纯”地表示有理数,即它将a / b的表示形式保持为(a, b)
,并实现了常用的运算符+, -, *, /
和其他运算符来处理这些元组,而不是评估每个操作的实际分数。
现在假设我想定义将Rational
实例添加到Int
时发生的情况,以及已经为Rational
添加到Rational
的行为。然后,当然,我可能最终想将Rational
添加到Double
,或添加到Float
,BigInt
其他数字类型...
+(Rational, _)
的几种实现方式:def + (that:Rational):Rational = {
require(that != null, "Rational + Rational: Provided null argument.")
new Rational(this.numer * that.denom + that.numer * this.denom, this.denom * that.denom)
}
def + (that:Int): Rational = this + new Rational(that, 1) // Constructor takes (numer, denom) pair
def + (that:BigInt): Rational = ....
.
.
.
Any
上的模式匹配:def + (that:Any):Rational = {
require(that != null, "+(Rational, Any): Provided null argument.")
that match {
case that:Rational => new Rational(this.numer * that.denom + that.numer * this.denom, this.denom * that.denom)
case that:Int | BigInt => new Rational(this.numer + that * this.denom, this.denom) // a /b + c = (a + cb)/b
case that:Double => ....
.
.
.
case _ => throw new UnsupportedOperationException("+(Rational, Any): Unsupported operand.")
}
}
我从模式匹配方法中看到的一个好处是可以节省实际的源代码行,但可能会降低可读性。也许更关键的是,当我获得未定义行为+
的类型时,我可以控制自己的工作。我不确定通过第一种方法如何实现,也许通过在所有其他方法的下面添加Any
的重载?无论哪种方式,听起来都是危险的。
关于应该选择第一种方法还是第二种方法的想法?我没有看到任何安全问题吗?我是否愿意接受ClassCastException
或其他类型的例外?
答案 0 :(得分:3)
强制执行编译时错误的方法是确保plus
方法实际上不能通过类型约束,隐式参数等接受类型Any
。
一种解决方法是利用scala Numeric
类型类。最好为Rational
创建一个实例,因为您可以轻松实现所有必需的方法,并且此时可以将plus
定义为
def +[T: Numeric](that: T) : Rational
您现在还可以提取隐式toInt
参数的toLong
/ toFloat
/ toDouble
/ Numeric
方法来处理未知类,如果需要的话,也不会抛出运行时错误-即使您不这样做,也至少可以显着减少可以传递的错误类型。
您还可以定义自己的类型类,并为要支持的类型定义适当的实例。然后,您可以将加法逻辑保留在+
方法中,或将其移入类型类实例:
trait CanBeAdded[T] {
def add(t: T, rational: Rational) : Rational
}
object CanBeAdded {
implicit val int = new CanBeAdded[Int] {
override def add(t: Int, rational: Rational): Rational = ???
}
implicit val long = new CanBeAdded[Long] {
override def add(t: Long, rational: Rational): Rational = ???
}
implicit val rational = new CanBeAdded[Rational] {
override def add(t: Rational, rational: Rational): Unit = ???
}
}
case class Rational(a: BigInt, b: BigInt) {
def +[T: CanBeAdded](that: T) = implicitly[CanBeAdded[T]].add(that, this)
}
我喜欢第二种选择,因为我不得不怀疑允许将您的Rational
类型添加到任何数字类型是否有意义。您提到您希望+
能够接受Double
s,但是精确的表示形式与通常在Double
s中出现的舍入误差相结合似乎会导致某些奇怪而违反直觉的行为,结果没有多大意义。