6

I have the following Scala code:

import java.util.{Comparator, function}

object ComparatorTest {
  Comparator.comparing[Int, String](new function.Function[Int, String] {
    override def apply(t: Int): String = t.toString
  })
}

which compiles without issue. I think I should be able to replace that Function with a lambda (that's what I understand from the doc, at least, and IntelliJ is of the same opinion). However, when I replace it with

  Comparator.comparing[Int, String]((t: Int) ⇒ t.toString)

I get a compiler error:

Error:(6, 23) overloaded method value comparing with alternatives:
  (x$1: java.util.function.Function[_ >: Int, _ <: String])java.util.Comparator[Int] <and>
  (x$1: java.util.function.Function[_ >: Int, _ <: String],x$2: java.util.Comparator[_ >: String])java.util.Comparator[Int]
 cannot be applied to (Int => String)
  Comparator.comparing[Int, String]((t: Int) ⇒ t.toString)

(It looks to me like the first alternative should match.)

I've just asked a similar question, and the solution was to explicitly specify type parameters, but in this case, I think I've specified everything I could. Is there another solution to use a lambda, or is an explicit Function needed here? If the latter, is there a doc that explains when exactly a lambda can be substituted for a SAM?

2 Answers 2

5

TL;DR: Use Ordering rather than using Comparator directly.


You can use type ascription to force it to recognize your argument as a java.util.function.Function[Int, String]:

Comparator.comparing[Int, String]((_.toString) : function.Function[Int, String])

Note that the parentheses around _.toString are required; otherwise, it tries to apply the ascription to the expression in the lambda rather than the lambda as a whole. This code is basically equivalent to:

val tmp: function.Function[Int, String] = _.toString
Comparator.comparing[Int, String](tmp)

However, when writing Scala, is generally better to use Ordering. Since Ordering extends Comparator, it can be used anywhere a Comparator is expected. You could use it like:

val cmp: Comparator[Int] = Ordering.by(_.toString)

or:

val cmp = Ordering.by[Int, String](_.toString)

As you can see, the type inference works far better here.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! Yes, the reason I sometimes use Comparators is because they have the thenCompare method which I find useful for building them and doesn't play nice with Orderings. Scala 2.13 will have it for Orderings, but who knows when it'll be out :p
@zale In the meantime, you can use tuples to build Orderings like that, e.g. Ordering.by(user => (user.lastName, user.firstName)) for Ordering.by(_.lastName).orElseBy(_.firstName) (though I admit it's less readable).
1

If the reason for using Comparator instead of Ordering is thenCompare, that is easily overcome with an implicit:

object PimpOrdering {
  implicit class OrderOrElse[T](val o: Ordering[T]) extends AnyVal {
   def orElse(o2: Ordering[T]) = new Ordering[T] {
     def compare(a: T, b: T) = {
        val cmp = o.compare(a,b)
        if (cmp == 0) o2.compare(a,b) else cmp
     }
   }
   def orElse[S : Ordering](f: T => S) = orElse(Ordering.by(f))
 }
}

Now you can write things like

Ordering.by[Foo, String](_.foo).orElse(_.bar).orElse(_.baz)

etc

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.