scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.88k stars 1.06k forks source link

extension methods should not pollute global scope #17659

Closed scf37 closed 1 year ago

scf37 commented 1 year ago

Right now extension methods are translated to ordinary functions and can be called as such:

extension (s: String) {
  def sayHello: String = s"Hello, $s"
}
"world".sayHello // Hello, world
sayHello("world") // Hello, world

Which limits its usage in libraries - for example, re-implementing scala.collectionStringOps as extension functions will add tenths of useless functions to visible scope causing confusion and potential problems with overload resolution and compatibility.

bishabosha commented 1 year ago

you can define your extension method in a non global scope such as a standalone object (i.e. not a companion)

scf37 commented 1 year ago

But then I have to import it to use it? Importing translated function(s) as well.

I'd like to have globally visible extension but without globally visible translated function. Implicit conversions half-solve this problem as implicit conversion function can be arbitrarily named and there is only one implicit function for entire StringOps.

bishabosha commented 1 year ago

I'm not really sure what is a new concern here?, all the extension methods added by Predef.augmentString (i.e. scala.collection.StringOps) should already be visible in completions

scf37 commented 1 year ago

New concern is dotty extension methods are kind of flawed for providing extensions from libraries.

Example: If StringOps are to be reimplemented as extension methods, it will add a lot of functions like def capitalize:(s: String) String and def ++[B >: Char](s: String, suffix: Iterable[B]): immutable.IndexedSeq[B] to global scope.

bishabosha commented 1 year ago

as I said, those methods are already visible without an import, so what is new? why does it matter which scope it is defined in, surely use-site is more important

bishabosha commented 1 year ago

if the concern is about documentation, I believe scaladoc has a grouping mechanism to organise definitions

scf37 commented 1 year ago

those methods are already visible without an import,

that is not true: https://scastie.scala-lang.org/R1Y6wnHlQZuYeQjDMEFgBw

bishabosha commented 1 year ago

ok, I see what you mean now - its not about members added to objects, but about what values exist to choose from as you start typing anywhere

demo here: https://scastie.scala-lang.org/Kg4wsf6VSdWa1o0fD9XBiA

scf37 commented 1 year ago

Indeed. I'm not sure why it was decided to make extensions available as functions as well but I don't think that is desirable in most cases. Surely I can stick to implicit anyval classes buuut why can't it be better than that.

som-snytt commented 1 year ago

Related discussion https://contributors.scala-lang.org/t/change-shadowing-mechanism-of-extension-methods-for-on-par-implicit-class-behavior/5831/24

It also took me a while to understand the objection here: "pollute global scope" means "whenever the extension is in lexical scope, the 'ordinary method name' is also in scope."

I still don't understand why implicit scope seems under-leveraged, but the inclination is to prefer imports.

scf37 commented 1 year ago

@som-snytt Yep.

Imagine a library with API of some traits and lots of extension methods, Kotlin uses this style extensively for collections, for example.

With current extension methods implementation, that API will consists of traits, extension methods and public functions with names derived from extension methods. They are unwanted since a) they bloat the API b) it is highly unlikely any client will use them directly instead of extension syntax c) they make keeping backward compatibility harder d) they can lead to overload resolution conflicts.

Honestly I don't understand why it is not obvious.

SethTisue commented 1 year ago

relevant SIP: https://github.com/scala/improvement-proposals/pull/60