Suppose that I have a class called Rational which represents rational numbers "purely", i.e it maintains the representation of a/b as (a, b) and implements the usual operators +, -, *, / and others to work on those tuples, instead of evaluating the actual fractions on every operation.
Suppose now that I want to define what happens if I add a Rational instance to an Int, in addition to the already defined behavior for Rational added to Rational. Then, of course, I might end up wanting to add Rational to Double, or to Float, BigInt other numeric types...
Approach #1: Provide several implementations of +(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 = ....
.
.
.
Approach #2: Pattern match on 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.")
}
}
One benefit I'm seeing from the pattern matching approach is saving in terms of actual source code lines, but perhaps with a decrease of readability. Perhaps more crucially, I have control over what I do when I'm provided with a type I haven't defined behavior of + for. I'm not certain how that could be attained via the first approach, perhaps by adding an overloading for Any underneath all the others? Either way, it sounds dangerous.
Ideas on whether one should opt for the first or second approach? Are there any safety issues I'm not seeing? Am I opening myself to ClassCastExceptions or other kinds of exceptions?
Rationalinstance to add it to an unsupported type, that is a compile-time error on their own front, yes?3.0you could use Union types to achieve that. However, you can hack something a sealedevtypeclass.