Reputation: 893
I'm trying to create a library that can convert distances from one unit into another. Ideally I'd be able to specify a distance in one unit, and when passed to a method that requires a different unit, have the scala compiler convert it automatically. This is what I have so far:
abstract class BaseUnit(scale: Option[Double] = None) {
def unit: String
def scalingFactor: Double = scale match {
case Some(factor) => factor
case None => 1.0
}
}
object Cm {
implicit def inch2cm(inch: Inch):Cm = new Cm(Some(0.393 * inch.scalingFactor))
}
class Cm(scale: Option[Double] = None) extends BaseUnit(scale) {
def unit: String = "cm"
}
object Inch {
implicit def cm2inch(cm: Cm):Inch = new Inch(Some(2.54 * cm.scalingFactor))
}
class Inch(scale: Option[Double] = None) extends BaseUnit(scale) {
def unit: String = "inch"
}
class Distance[A <: BaseUnit](val scalar: Double, val unit: A) {
override def toString: String = (scalar*unit.scalingFactor)+unit.unit
}
def foo(x: Distance[Cm], y: Distance[Cm]): String = x.toString()+","+y.toString()
Using it without explicitly stating the type parameter seems to make Scala use the Nothing
type:
val a = new Distance(10, new Inch)
println(foo(a, a))
> scala test.scala
found : this.Distance[Nothing]
required: this.Distance[this.Cm]
Note: Nothing <: this.Cm, but class Distance is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
println(foo(a, a))
^
one error found
Following the compiler's suggestion results in foo returning 10.0inch,10.0inch
rather than the expected 3.93cm,3.93cm
.
If I explicitly specify the type, the compiler picks up on the difference, but still doesn't implicitly convert one to the other.
val a = new Distance[Inch](10, new Inch)
println(foo(a, a))
// found : this.Distance[this.Inch]
// required: this.Distance[this.Cm]
// println(foo(a, a))
// ^
// one error found
Am I doing something wrong, or does the compiler not allow this usage of implicit conversion?
Upvotes: 0
Views: 3340
Reputation: 167911
You just need to
class Distance[A <: BaseUnit](val scalar: Double, val unit: A) { ... }
so that the compiler has a reason to not make A
too specific. Otherwise it's free to choose Nothing
since it's not related to anything that you're doing.
Also, you know how to convert between units, but you haven't taught it how to convert between distances. You can:
implicit def convertDist[A <: BaseUnit, B <: BaseUnit](da: Distance[A])(implicit a2b: (A => B)): Distance[B] = new Distance[B](da.scalar, a2b(da.unit))
or something like that. (As you define it now, the conversions are backwards, incidentally.)
Upvotes: 1