Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala type check complaining with lambda, not with explicit Function

Tags:

scala

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?

like image 742
zale Avatar asked Feb 09 '19 21:02

zale


2 Answers

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.

like image 127
Brian McCutchon Avatar answered Nov 15 '22 08:11

Brian McCutchon


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

like image 27
Dima Avatar answered Nov 15 '22 06:11

Dima