Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler do not recognize function overloading because types are erased. How to overcome this?

I have a problem that the two methods named fooSome in the code below don't compile as the compiler reports a problem with duplicate method names:

class Foo() {

  // variable block has 2 closure variables
  def fooSome(block: Some[(Int, String) => Unit]) = {

  }

  // variable block has 1 closure variables
  def fooSome(block: Some[Int => Unit]) = {

  }

  // variable block has 2 closure variables
  def fooNoSome(block: (Int, String) => Unit) = {

  }

  // variable block has 1 closure variables
  def fooNoSome(block: Int => Unit) = {

  }
}

On the contrary the compiler reports no such method name collision with the two methods named fooNoSome. So the problem is that the compiler doesn't see a difference between "Some[(Int, String) => Unit]" and "Some[(Int) => Unit]" whereas "(Int, String) => Unit" is seen as a different signature than "(Int) => Unit" as for the fooNoSome methods.

I could work around this by creating a class Some2Args that is used for the "Some[(Int, String) => Unit]" case and a class Some1Arg for the "Some[(Int) => Unit]" case.

My question is whether there is a more elegant and less effortful solution.

like image 366
OlliP Avatar asked Jan 27 '14 08:01

OlliP


2 Answers

The compiler does see a difference between them, it is just not allowed to use this difference in overloading (since erasures of Some[(Int, String) => Unit] and Some[Int => Unit] are the same and JVM doesn't allow overloading when erasures of arguments are the same). The solution is to add fake implicit arguments:

class Foo() {
  def fooSome(block: Some[(Int, String) => Unit]) = {

  }

  def fooSome(block: Some[Int => Unit])(implicit d: DummyImplicit) = {

  }
}

Also note that erasures of fooNoSome are fooNoSome(Function2) and fooNoSome(Function1), so if you wanted to add another overload which takes any function of one or two arguments, you'd need the DummyImplicit trick again:

  def fooNoSome(block: Double => Unit)(implicit d: DummyImplicit) = ...
like image 89
Alexey Romanov Avatar answered Sep 28 '22 07:09

Alexey Romanov


A more systematic approach, without dummy implicits, would be something like this:

class Foo() {
  def fooSome[X](block: Some[X])(implicit cfs: CanFooSome[X]) = {
    cfs.handleThat(block)
  }
}

trait CanFooSome[X] {
  def handleThat(block: Some[X]): Unit // or other return type
}

// in carefully chosen scope for implicits
implicit object CanFooSomeIntToUnit extends CanFooSome[Int => Unit] {
  def handleThat(block: Some[Int => Unit]) = ...
}

// same for all other type combinations, like (Int, String) => Unit etc.

This works because it's the Scala compiler that figures out at compile time what to plug in for the CanFooSome implementation, the JVM does not see anything of that at runtime. This pattern can have certain advantages (e.g. extensibility: one can provide new CanFooSome's from the outside, without ever altering the code of Foo), and it does not get messier as the number of types for fooSome increases.

like image 27
Andrey Tyukin Avatar answered Sep 28 '22 06:09

Andrey Tyukin