Jxtps
Jxtps

Reputation: 704

Getting a Numeric[T] for generic T (if available) in Scala

I need to get a Numeric[T] if available (so I guess an Option[Numeric[T]]) for the following situation:

class Foo[T]() {
  def t:T
  def doSomeProcessing():Unit = ... // This is where I need an Option[Numeric[T]]
}

class Bar {
  def foos: Seq[Foo[_]]
  def doSomethingWithFoos():Unit = {
    for (foo <- foos) {
      foo.doSomeProcessing()
    }
  }
}

I can add ClassTag or TypeTag as necessary / helpful, but I'd like to avoid an explicit listing of each and every possible Numeric, which is what I have right now:

val numOpt: Option[Numeric[T]] = classTag[T] match {
  case longTag if longTag == classTag[Long] => Some(Numeric.LongIsIntegral.asInstanceOf[Numeric[T]])
  case intTag if intTag == classTag[Int] => Some(Numeric.IntIsIntegral.asInstanceOf[Numeric[T]])
      ...
  case _ => None
}

Is there a more elegant / comprehensive solution? Thanks!

Edit: I tried doing:

class Foo[T](implicit num: Numeric[T] = null) {
  def t:T
  val numOpt:Option[Numeric[T]] = Option(num)
}

But that didn't work (numOpt = None for T = Long), probably because Foo is actually part of a long class hierarchy with multiple this / super calls...

Update: the issue is that Foo actually has four constructors, and all need to take an (implicit num: Numeric[T] = null), but only one of them is allowed to have a default parameter. I guess I would have to find a way to get rid of three of them, or manually feed in null as needed.

Upvotes: 0

Views: 78

Answers (1)

user
user

Reputation: 7604

Try an implicit Numeric parameter with a default value:

class Foo[T] private(val t: T)(val numeric: Option[Numeric[T]])
object Foo {
  def apply[T](t: T)(implicit numeric: Numeric[T] = null) = new Foo(t)(Option(numeric))
}

You can use it as such

class Bar {
  //Note that I've explicitly put in Foo[Int] here because it doesn't understand it otherwise (not sure why).
  def foos: Seq[Foo[_]] = Seq(Foo("blah"), Foo[Int](39))
  def doSomethingWithFoos():Unit = {
    for (foo <- foos) {
      println(foo.t)
      foo.numeric match {
        case Some(num: Numeric[foo.t.type]) => println(num.plus(foo.t, foo.t))
        case None => println("Not numeric")
      }
    }
  }
}

Output:

blah
Not numeric
39
78

<script src="https://scastie.scala-lang.org/CDSbri6WQwSYtIoR4Swl8A.js"></script>

If you don't want the boilerplate of an apply method, you can also define your own method to supply Option instances and directly use an implicit in your constructor.


class Foo[T](val t: T)(implicit val numeric: Option[Numeric[T]])

implicit def implicitOption[T](implicit t: T = null): Option[T] = Option(t)

Link to Scastie: https://scastie.scala-lang.org/pUOw5tFNRtGZVbHNHx7QYw

Otherwise, you can use a nullable type instead of an Option.

class Foo[T](val t: T)(implicit val numeric: Numeric[T] = null)

Link to Scastie: https://scastie.scala-lang.org/AOGNZSirT0C7Cy0Po6vZXw

Upvotes: 2

Related Questions