Allow top-level "implicit class"es by folding them into containing package-object

65 views
Skip to first unread message

Ryan Williams

unread,
Dec 6, 2016, 10:26:24 PM12/6/16
to scala-language
The requirement that implicit classes be explicitly declared inside another object gets in my way frequently and feels like it could be relaxed.

My typical library code looks like:

package foo.bar

object Baz {
 
implicit class Baz(…) { }
}

and corresponding calling code:

import foo.bar.Baz.Baz

One of the two Baz-layers is redundant.

Logically, i want my implicit classes to be considered to live inside their nearest containing package object (in this case foo.bar), just like non-implicit classes, but I can only declare a given package object once, so to use it I have to put all classes in a package in the same file (in the package object), which is also suboptimal.

LMK if I'm missing something? Thanks!

Jason Zaugg

unread,
Dec 6, 2016, 11:29:13 PM12/6/16
to scala-language

Unfortunately we have a number of assumptions in our toolchain (mostly driven by the requirement of separate+incremental compilation), that each class file comes from a single source file. Package objects are modelled as an object with the name package, so like any other object they must be defined within a single source file (by convention, named package.scala.)


I believe instead that we should allow top level implicit classes and treat it as though it introduced companion object of type Function1[Wrapped, Wrapper]. The companion itself would be seen as an implicit. The call to Wrapper.apply(wrapped) couId be translated to new Wrapper(wrapped) in order to make later optimizations for implicit classes work as expected. We already do this for calls to the companion apply methods of case classes.

-jason


--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

martin odersky

unread,
Dec 7, 2016, 3:51:34 AM12/7/16
to scala-l...@googlegroups.com
On Wed, Dec 7, 2016 at 5:28 AM, Jason Zaugg <jza...@gmail.com> wrote:

Unfortunately we have a number of assumptions in our toolchain (mostly driven by the requirement of separate+incremental compilation), that each class file comes from a single source file. Package objects are modelled as an object with the name package, so like any other object they must be defined within a single source file (by convention, named package.scala.)


I believe instead that we should allow top level implicit classes and treat it as though it introduced companion object of type Function1[Wrapped, Wrapper]. The companion itself would be seen as an implicit. The call to Wrapper.apply(wrapped) couId be translated to new Wrapper(wrapped) in order to make later optimizations for implicit classes work as expected. We already do this for calls to the companion apply methods of case classes.


But there's another problem: How do you figure out that a top-level class is implicit? The compiler has only the name of the file as an entry in the enclosing package scope. It would have to look inside the file to find out whether it contained an implicit class. That would break all our assumptions about separate compilation.

  - Martin
 

On Wed, 7 Dec 2016 at 13:26 Ryan Williams <ryan.blak...@gmail.com> wrote:
The requirement that implicit classes be explicitly declared inside another object gets in my way frequently and feels like it could be relaxed.

My typical library code looks like:

package foo.bar

object Baz {
 
implicit class Baz(…) { }
}

and corresponding calling code:

import foo.bar.Baz.Baz

One of the two Baz-layers is redundant.

Logically, i want my implicit classes to be considered to live inside their nearest containing package object (in this case foo.bar), just like non-implicit classes, but I can only declare a given package object once, so to use it I have to put all classes in a package in the same file (in the package object), which is also suboptimal.

LMK if I'm missing something? Thanks!

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-language+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-language+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--

Martin Odersky
EPFL and Lightbend

Roland Kuhn

unread,
Dec 7, 2016, 4:04:50 AM12/7/16
to scala-l...@googlegroups.com
7 dec. 2016 kl. 09:51 skrev martin odersky <ode...@gmail.com>:



On Wed, Dec 7, 2016 at 5:28 AM, Jason Zaugg <jza...@gmail.com> wrote:

Unfortunately we have a number of assumptions in our toolchain (mostly driven by the requirement of separate+incremental compilation), that each class file comes from a single source file. Package objects are modelled as an object with the name package, so like any other object they must be defined within a single source file (by convention, named package.scala.)


I believe instead that we should allow top level implicit classes and treat it as though it introduced companion object of type Function1[Wrapped, Wrapper]. The companion itself would be seen as an implicit. The call to Wrapper.apply(wrapped) couId be translated to new Wrapper(wrapped) in order to make later optimizations for implicit classes work as expected. We already do this for calls to the companion apply methods of case classes.


But there's another problem: How do you figure out that a top-level class is implicit? The compiler has only the name of the file as an entry in the enclosing package scope. It would have to look inside the file to find out whether it contained an implicit class. That would break all our assumptions about separate compilation.

Would it be thinkable to forego separate compilation? Source files whose translation has not been invalidated since the last run could just be rehydrated from their class files (assuming that this can be made quick enough) or even kept in memory between runs. How close is this to what zinc does?

Regards,

Roland

To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.

Jason Zaugg

unread,
Dec 7, 2016, 5:31:56 AM12/7/16
to scala-l...@googlegroups.com
On Wed, 7 Dec 2016 at 18:51 martin odersky <ode...@gmail.com> wrote:
On Wed, Dec 7, 2016 at 5:28 AM, Jason Zaugg <jza...@gmail.com> wrote:

Unfortunately we have a number of assumptions in our toolchain (mostly driven by the requirement of separate+incremental compilation), that each class file comes from a single source file. Package objects are modelled as an object with the name package, so like any other object they must be defined within a single source file (by convention, named package.scala.)


I believe instead that we should allow top level implicit classes and treat it as though it introduced companion object of type Function1[Wrapped, Wrapper]. The companion itself would be seen as an implicit. The call to Wrapper.apply(wrapped) couId be translated to new Wrapper(wrapped) in order to make later optimizations for implicit classes work as expected. We already do this for calls to the companion apply methods of case classes.


But there's another problem: How do you figure out that a top-level class is implicit? The compiler has only the name of the file as an entry in the enclosing package scope. It would have to look inside the file to find out whether it contained an implicit class. That would break all our assumptions about separate compilation.

Good point. To fit in with that constraint, we'd need some scheme to encode the implicit modifier into the name of a some class. For instance, we could do something like:
implicit class RichString(val s: String) {
   /*synthetic*/ class Implicit$ // added by the compiler
}
There is precedent for adding synthetic inner classes (e.g javac does to add to the signatures of "access constructors": https://gist.github.com/retronym/8ca8026f5b7eb981b53f5c8923d50f54)

-jason

martin odersky

unread,
Dec 7, 2016, 5:35:37 AM12/7/16
to scala-l...@googlegroups.com

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-language+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

That's an interesting idea! I had not thought of that. That could indeed solve the problem.

 - Martin
Reply all
Reply to author
Forward
0 new messages