List.sum on custom class

2020-03-30 04:07发布

问题:

I have the following code that represents GF2 field:

trait GF2 {
  def unary_- = this
  def + (that: GF2): GF2
  def * (that: GF2): GF2

  def / (that: GF2) = that match {
    case Zero => throw new IllegalArgumentException("Div by 0")
    case _ => this
  }
}

object Zero extends GF2 {
  override def toString = "Zero"
  def + (that: GF2) = that
  def * (that: GF2) = this
}

object One extends GF2 {
  override def toString = "One"
  def + (that: GF2) = that match { case One => Zero ; case _ => this }
  def * (that: GF2) = that match { case One => this ; case _ => that }
}

And now I'd like to call this function: List(One, One, Zero, One).sum such that GF2._+ would be called for summation, how can I accomplish that? Should GF2 extend some interface or should I implemet type class technique?

回答1:

You need a Numeric[GF2] implicit:

trait GF2IsNumeric extends Numeric[GF2] {
  def plus(x: GF2, y: GF2): GF2 = x + y
  def minus(x: GF2, y: GF2): GF2 = x + (-y)
  def times(x: GF2, y: GF2): GF2 = x * y
  def negate(x: GF2): GF2 = -x
  def fromInt(x: Int): GF2 = ???
  def toInt(x: GF2): Int = ???
  def toLong(x: GF2): Long = ???
  def toFloat(x: GF2): Float = ???
  def toDouble(x: GF2): Double = ???
  override def zero = Zero
  override def one = One
}

trait GF2Ordering extends scala.math.Ordering[GF2] {
  override def compare(a: GF2, b: GF2) = if (a == b) 0 else if (b == One) 1 else -1
}

implicit object GF2IsNumeric extends GF2IsNumeric with GF2Ordering

Then you can do:

println(List(One, One, Zero, One).sum)
// One


回答2:

Look at the signature of sum:

def sum[B >: A](implicit num: Numeric[B]): B

I was about to suggest that you make GF2 a member of Numeric typeclass by providing and implicit value of type Numeric[GF2], but then I looked at the definition of Numeric and realized that it contains a ton of operations completely irrelevant to summation that you would have to implement.

I don't like this, I think sum method should require some more abstract typeclass (a monoid, perhaps?).

So, I think your best option (unless you want to implement entire Numeric instance) is to use either reduce (will work only with non-empty lists) or fold:

yourList.reduce(_ + _)
yourList.fold(Zero)(_ + _)


回答3:

You need to implement a version of Numeric for your trait in order for it to work. See here for the full definition you'll need to create.

 object InScope{
   implicit object GF2Numeric extends Numeric[GF2]{
     //..your implementation here
   }
 }

The full signature of sum on a List is actually:

 def sum(implicit num: Numeric[A])

Where by the A is the type of the List[A].