简体   繁体   中英

Arithmetics on a class hierarchy

How can I do arithmetics on classes included in a type hierarchy?

I have been trying to store different types of metrics in summaries, so that I could compute aggregates on each metric collection.

In the following I show my attempts at implementation:
In my first attempt I used only a simple class hierarchy

sealed trait Metric {
  def getValue(): Number = ???
}
class Counter(count: Int) extends Metric {
  override def getValue() : Int = count
}
class Meter(rate: Double) extends Metric {
  override def getValue() : Double = rate
}

class Container(val key: String, x : Metric, val y : Metric) {
  // ERROR: Type mismatch expected: String actually: Number
  def combine(): Number = x.getValue + y.getValue
}

object Main {
  var list : List[Container] = List()
  var res : Map[String, Number] = list.map(x => x.key -> x.combine()).toMap
}

My second attempt used generics

sealed trait Metric2[M] {
  def getValue(): M = ???
}
class Counter2(count: Int) extends Metric2[Int] {
  override def getValue() : Int = count
}
class Meter2(rate: Double) extends Metric2[Double] {
  override def getValue() : Double = rate
}

class Container2[T](val key : String, x : Metric2[T], y: Metric2[T]) {
  // ERROR: Type mismatch expected: String actually: Number
  def combine(): T = x.getValue + y.getValue
}

object Main2 {
  var list2 : List[Container2[Number]] = List()
  var res : Map[String, Number] = list2.map(x => x.key -> x.combine()).toMap
}

The errors are highlighted in the code.

To my understanding the Number class is not actually part of the scala type hierarchy and instead Numeric shoud be used. Maybe this is the place where my code takes a wrong turn. However I could not find a good tutorial on the usage of Numeric. Any hints are appreciated!

[EDIT:] Thanks to Alexey Romanov for the solution. I specified two versions of the container, one with implicts and context and one without.

sealed abstract class Metric[M] {
  abstract def value(): M
}
class Counter(count: Int) extends Metric[Int] {
  override def value() : Int = count
}
class Meter(rate: Double) extends Metric[Double] {
  override def value() : Double = rate
}
class Container[T](val key : String, x : Metric[T], y: Metric[T])(implicit ev: Numeric[T]) {
  def combine(): T = implicitly[Numeric[T]].plus(x.value, y.value)
}
class Container2[T: Numeric](val key : String, x : Metric[T], y: Metric[T]) {
  import scala.Numeric.Implicits._
  def combine(): T = x.value + y.value
}
object Main {
  var list : List[Container[Number]] = List()
  var res : Map[String, Number] = list.map(x => x.key -> x.combine()).toMap
}

The fixes required are pretty small:

sealed abstract class Metric2[M: Numeric] {
  def getValue(): M
}
// no changes to Counter2 or Meter2

import scala.Numeric.Implicits._
class Container2[T: Numeric](val key : String, x : Metric2[T], y: Metric2[T]) {
  def combine(): T = x.getValue + y.getValue
}

The : Numeric syntax is a context bound and the import is used to provide the nice operator syntax (you would need to call the Numeric methods directly without it).

As a side note:

  1. getValue() should be abstract, this default implementation is actively harmful by allowing you not to implement it;

  2. it's better Scala style to call it value without parentheses.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM