Cast a spell with @targetName to eliminate an error

Suppose i want to overload a function that gives trouble because of type erasure:

object test :
  def f(list: List[Int])    = list.sum.toString
  def f(list: List[String]) = list.mkString(",") 

Compiling gives the expected error together with the hint how to solve this: Consider adding a @targetName annotation. Nice! So indeed this solves the problem:

object test :
  def f(list: List[Int])    = list.sum.toString
  @targetName("HocusPocus")
  def f(list: List[String]) = list.mkString(",")

And yes, it is also well documented.

But what i fail to see is, if the given name in @targetName is never used, and is just there to fix and mask this limitation in function overloading, why can this not be done directly by the compiler? Giving a name that is never used just does not feel ‘Scala’ where at times a lot of machinery is in place to remove unused names and the related boilerplate.

1 Like

The name is actually used at the bytecode and you should pick a good one, it is also the name that will be used to call your method from other languages.

3 Likes

If it weren’t for this point I would definitely be in favor of just letting the compiler generate different names when necessary.

2 Likes

Exactly this. I do not have statistics, but my feeling is that the number of times this trick is used to solve the overload error and at the same time the method is called from an other language are negligible. So, if you need the name yourself, fine, specify it, and in other cases let the compiler generate one silently for this situation.

1 Like

@devlaam @Jasper-M my point is that it also affects binary compatibility, so now the compiler would need to somehow keep track of the previous name, that would be a hassle for library maintainers.

For user code there are many more ways to solve this problem, including:

  • The dummy implicit
  • Match types
  • A typeclass
  • The magnet pattern
  • Or simply using different names, which is all that targetName does.
2 Likes

Sure but the compiler already does name mangling in some cases, and in Scala 3 name generation for givens.

Ah, so you suggest the generation to be deterministic. That is actually a good idea.
It could be that @targetName("") would mean “let the compiler auto-generate the name”.

1 Like

Okay, but for which situations would @targetName("") be a solution except for the case i came up with?

My point is: if you have to add some (fixed) code just to remove an error, let the compiler do this under the hood. Just like you don’t have to define an apply method with the same parameters as the given case class definition. If you need other parameters, sure, then it is logical. Likewise, if you want to give the name to the overloaded method yourself, sure, then a @targetName("my_name") is called for.

That there are other ways to solve the overload problem is beside the point. There are always other solutions to a given problem. Taken to the extreme, all problems solved presently by Scala can surely be solved some other way. Plenty of other languages out there :slightly_smiling_face:

My point was that targetName is the least desired of all solutions, but that is probably just MHO.

Type erasure exists for a reason and I would say that letting the compiler automatically “fix” all situations in which this happens is not a good idea: although it may also be that I personally don’t use overloading that much.

In particular, I am worried that if this is the default behavior people would rely on it too much without noticing, and then they may break the binary compatibility of a library without noticing it.

1 Like

I understand. These are indeed valid arguments. Overloading is debatable, and every developer should use it sparsely. And binary compatibility should not lead to unpleasant surprises.

On the other hand, if a language chooses to support a certain feature, then it should be supported well. And the developer should not have to jump through hoops to be able to use it. In this respect i really dislike the dummy implicits. If your particular DSL has a few versions of some method the number of implicits scale with the versions. Yuk!

Personally, i am in favour of orthogonal language properties. Just as Scala tries to hide the difference between integers as primitive or object, by automatic (un)boxing, i do not want to see this difference pop up in method overloading all of a sudden. The type of the parameter in the overloaded method should not require extra boilerplate code in just some situations. But, this is just MHO.

On the other hand, if a language chooses to support a certain feature, then it should be supported well.

Agree, but that is not the case here.
What feature is the language not supporting well?

Overload + erasure doesn’t work, period; it is in the language definition. And targetName is not exactly a feature to overcome erasure, is a feature to control the name of a method in the binary code; and it does that exactly well.
You can use that overcome erasure in overloads in the same way you would do without the annotation, using a different name to the overload. Here, the only difference is that the different name is not visible in the source code (which I would say is a bad idea in general, thus the reason why I say this should not be the default behavior)

Now, if the badly supported feature you are referring to is overloading… then that is a big discussion, for the record I know that removing it would simplify the compiler a lot so yeah some people have considered dropping it… but I doubt this is what you meant.
And, again, type erasure is a conscious design decision, not a casualty. Thus, its current behavior in relationship with overloading is expected, just like its behavior with pattern matching.

Finally, I stand by my point that there are better ways to solve this problem namely implicits and match types.

1 Like