1

I've got a trait implementing an Ordered trait of Scala:

package stackQuestions

trait ValueTrait[TYPE] extends Ordered[ValueTrait[TYPE]]{
  def value: Double
}

and a subclass:

package stackQuestions

class Value[A](list: List[A], function: (A, A) => Double) extends ValueTrait[A] {
  private val _value: Double = list.zip(list.tail).map(pair => function(pair._1, pair._2)).sum

  override def value: Double = _value

  override def compare(that: ValueTrait[A]): Int = {
    (this.value - that.value).signum
  }
}

Basically, when an object of Value is created with provided function, the value is calculated. What I want to achieve is to sort a collection of Value objects based on their value. This should be guaranteed by Ordered trait. I've written some simple tests for this:

package stackQuestions

import org.scalatest.FunSpec

class ValueTest extends FunSpec {
  def evaluationFunction(arg1: Int, arg2: Int): Double = {
    if (arg1 == 1 && arg2 == 2) return 1.0
    if (arg1 == 2 && arg2 == 1) return 10.0
    0.0
  }

  val lesserValue = new Value(List(1, 2), evaluationFunction) // value will be: 1.0
  val biggerValue = new Value(List(2, 1), evaluationFunction) // value will be: 10.0

  describe("When to Value objects are compared") {
    it("should compare by calculated value") {
      assert(lesserValue < biggerValue)
    }
  }
  describe("When to Value objects are stored in collection") {
    it("should be able to get max value, min value, and get sorted") {
      val collection = List(biggerValue, lesserValue)

      assertResult(expected = lesserValue)(actual = collection.min)
      assertResult(expected = biggerValue)(actual = collection.max)

      assertResult(expected = List(lesserValue, biggerValue))(actual = collection.sorted)
    }
  }
}

However, when sbt test -Xlog-implicits I've get error messages:

[info] Compiling 1 Scala source to /project/target/scala-2.11/test-classes ...
[error] /project/src/test/scala/stackQuestions/ValueTest.scala:24:64: diverging implicit expansion for type Ordering[stackQuestions.Value[Int]]
[error] starting with method $conforms in object Predef
[error]       assertResult(expected = lesserValue)(actual = collection.min)
[error]                                                                ^
[error] /project/src/test/scala/stackQuestions/ValueTest.scala:25:64: diverging implicit expansion for type Ordering[stackQuestions.Value[Int]]
[error] starting with method $conforms in object Predef
[error]       assertResult(expected = biggerValue)(actual = collection.max)
[error]                                                                ^
[error] /project/src/test/scala/stackQuestions/ValueTest.scala:27:83: diverging implicit expansion for type scala.math.Ordering[stackQuestions.Value[Int]]
[error] starting with method $conforms in object Predef
[error]       assertResult(expected = List(lesserValue, biggerValue))(actual = collection.sorted)
[error]                                                                                   ^
[error] three errors found
[error] (Test / compileIncremental) Compilation failed
[error] Total time: 1 s, completed 2018-09-01 08:36:18

I've dug for similar problems and after reading:

I get to know that the compiler is confused about how to choose the proper function for comparison. I know that I can circumvent this using sortBy(obj => obj.fitness) but is there any way to use less verbose sorted method?

Koikos
  • 162
  • 9

1 Answers1

3

Scala uses Ordering[T] trait for methods sorted, min and max of a collection of type T. It can generate instances of Ordering[T] automatically for T's that extend Ordered[T].

Because of Java compatibility Ordering[T] extends java.util.Comparator[T], which is invariant in T, so Ordering[T] has to be invariant in T as well. See this issue: SI-7179.

This means that Scala can't generate instances of Ordering[T] for T's that are subclasses of classes that implement Ordered.


In your code you have val collection = List(biggerValue, lesserValue), which has type List[Value[Int]]. Value doesn't have its own Ordered or Ordering, so Scala can't sort this collection.

To fix you can specify collection to have type List[ValueTrait[Int]]:

val collection = List[ValueTrait[Int]](biggerValue, lesserValue)

Or define an explicit Ordering for Value[T]:

object Value {
  implicit def ord[T]: Ordering[Value[T]] = 
    Ordering.by(t => t: ValueTrait[T])
}

You can also consider using a different design in this problem, if it suits your other requirements:

In your code all instances of ValueTrait[TYPE] have a value of type Double, and the distinctions in subclass and TYPE don't seem to be important at runtime. So you can just define a case class Value(value: Double) and have different factory methods to create Value's from different kinds of arguments.

case class Value(value: Double) extends Ordered[Value] {
  override def compare(that: Value): Int = this.value compareTo that.value
}

object Value {
  def fromList[A](list: List[A], function: (A, A) => Double): Value =
    Value((list, list.tail).zipped.map(function).sum)
} 

And the usage:

scala> val lesserValue = Value.fromList(List(1, 2), evaluationFunction)
lesserValue: Value = Value(1.0)

scala> val biggerValue = Value.fromList(List(2, 1), evaluationFunction)
biggerValue: Value = Value(10.0)

scala> val collection = List(biggerValue, lesserValue)
collection: List[Value] = List(Value(10.0), Value(1.0))

scala> (collection.min, collection.max, collection.sorted)
res1: (Value, Value, List[Value]) = (Value(1.0),Value(10.0),List(Value(1.0), Value(10.0)))
Kolmar
  • 14,086
  • 1
  • 22
  • 25
  • Thank you for your time and answer. The first solution works fine for me. But I'm also really thankful for the second approach presented. Here I've tailored my real code to show only the necessary elements to reproduce the problem I'm facing; the reason for using generics is not shown here. However, your example makes me re-think if I really need my approach and if proposed isn't better. Again, thank you, sir. – Koikos Sep 01 '18 at 11:52