0

I am trying to create a generic class that only accepts java.math.BigDecimal or Long. Here is the code:

class myClass[T]()
{ 
    def display( x : T) = {
      println(x.doubleValue())
    }
}


val input = new java.math.BigDecimal(100)
// val input = 100L

val x = new myClass[java.math.BigDecimal]()
x.display(input)

Clearly I will have this error: ScalaFiddle.scala:22: error: value doubleValue is not a member of type parameter T.

I tried playing with implicit conversion, view bound, and context bound for hours. No result so far. Is there any way I can force Scala to believe me that T has method .doubleValue()? (java.big.Decimal and Long both has .doubleValue() method, but they don't share same super-class)

bbbbz
  • 5
  • 2

3 Answers3

3

Try structural type bound

class myClass[T <: {def doubleValue(): Double}]

or type class

trait HasDoubleValue[T] {
  def doubleValue(t: T): Double
}
object HasDoubleValue {
  implicit val long: HasDoubleValue[Long] = t => t.doubleValue
  implicit val bigDecimal: HasDoubleValue[BigDecimal] = t => t.doubleValue
}

implicit class DoubleValueOps[T: HasDoubleValue](x: T) {
  def doubleValue(): Double = implicitly[HasDoubleValue[T]].doubleValue(x)
}

class myClass[T: HasDoubleValue]
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 1
    Number has doubleValue. Then expose only constructors taking Long and BigDec, whatever. – som-snytt Feb 06 '20 at 01:08
  • Hi Dmytro, thanks for your answer. For your first solution, if passing in Long, I will have this error - error: type arguments [scala.this.Long] do not conform to class myClass's type parameter bounds [T <: scala.this.AnyRef{ def doubleValue(): scala.this.Double}]. I tried to change Double to java.lang.Double but Scala is still unhappy. My guess is that Long in scala is inherited from Java side. Thus AnyRef is not the super class of it? – bbbbz Feb 06 '20 at 15:06
  • @bbbbz Try `val input1 = 100L` `val x1 = new myClass[java.lang.Long]()` `x1.display(input1)` – Dmytro Mitin Feb 06 '20 at 15:21
2

In Dotty (Scala 3) we might use union types, for example,

class myClass[T <: (Long | java.math.BigDecimal)]() { 
  def display(x: T) = 
    println(
      x match {
        case t: Long => t.doubleValue
        case t: java.math.BigDecimal => t.doubleValue
      }
    )
}

new myClass().display(new java.math.BigDecimal(100))   // OK
new myClass().display(100L)                            // OK
new myClass().display("100")                           // Error
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
2
scala> class C private (n: Number) {
     | def this(i: Long) = this(i: Number)
     | def this(b: BigDecimal) = this(b: Number)
     | def d = n.doubleValue
     | }
defined class C

scala> new C(42L).d
res0: Double = 42.0

scala> new C(BigDecimal("123456789")).d
res1: Double = 1.23456789E8

or with a type parameter

scala> class C[A <: Number] private (n: A) { def d = n.doubleValue ; def a = n } ; object C {
     | def apply(i: Long) = new C(i: Number) ; def apply(b: BigDecimal) = new C(b) }
defined class C
defined object C
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • Thanks for the solution! Seems it works only if I explicitly pass in a variable with defined data type to new C().d. In my case, if I replace "x.doubleValue()" to "C(x).d", Scala will show this error: error: overloaded method constructor C with alternatives: ( b: BigDecimal)ScalaFiddle.this.C ( i: scala.this.Long)ScalaFiddle.this.C cannot be applied to (T) – bbbbz Feb 06 '20 at 15:11