Protect implicit def/vals/classes from direct user calls

Usually when we write implicit def/vals/classes they are meant to be used implicilty and not directly.
Assume the following API exists in a library Liberation:

package Libration

trait Foo[T]
object Foo {
  implicit def ev[T] : Foo[T] = ???
  implicit def fromInt(i : Int) : Foo[Int] = ???
  protected[Liberation] val protVal : Int = ???
  val pubVal : Int = ???
  def pubDef : Int = ???
}

The problem

The user gets a cluttered API and is able to access Foo.pubVal, Foo.ev, and Foo.fromInt (not Foo.protVal).
image
I also want to prevent the user from calling (some) implicit definitions directly, but still enable calling them implicitly to avoid bad usage, e.g.:

implicitly[Foo[Int]] //should compile
val f : Foo[Int] = 1 //should compile
Foo.ev[Int] //if protected should not compile
Foo.fromInt(1) //if protected should not compile

Possible language solution

In similar fashion that we limit protVal access outside the Liberation scope, we can provide syntax that limits implicits access outside the implicit scope, via protected[implicit]. E.g.:

package Libration

trait Foo[T]
object Foo {
  protected[implicit] implicit def ev[T] : Foo[T] = ???
  protected[implicit] implicit def fromInt(i : Int) : Foo[Int] = ???
  protected[Liberation] val protVal : Int = ???
  val pubVal : Int = ???
  def pubDef : Int = ???
}

image

Workarounds

As suggested on the scala/scala gitter, it is possible to give the implicits strange names or use a prefix of _, making it hard for the user to call them and push them to the bottom of the auto-completion list. E.g.:

package Libration

trait Foo[T]
object Foo {
  implicit def _ev[T] : Foo[T] = ???
  implicit def _fromInt(i : Int) : Foo[Int] = ???
  protected[Liberation] val protVal : Int = ???
  val pubVal : Int = ???
  def pubDef : Int = ???
}

image

As you can see, it looks much nicer with the clean API.


Discussed first at the scala/scala gitter.

This sounds like a massive workaround to the problem of “I’d like to be able to hide things from users in IntelliJ’s autocompleter”, to which I suggest the better solution of “add support to intellij for ignoring things”.

Worse, implementing this in the scala compiler demands that the work be duplicated in IntelliJ.

Just send a small PR to intellij-scala to allow ignoring implicit methods in completions…

Rockfact: I implemented this feature in NetBeans, back in the day.

7 Likes

Categorically no

Being able to explicitly provide implicits is a core debugging/troubleshooting technique, one which is prevented by this proposal.

We also already have techniques for de-cluttering APIs. Implicits can be put in a dedicated nested Implicits singleton, or defined on a companion, or provided via a package object.

8 Likes

There you go:

scala> object Module { 
     |   type Foo[T] = Internal.Foo[T]
     |   object Foo {
     |     def pubMethod = "Hi!"
     |   }
     | 
     |   private[Module] object Internal {
     |     trait Foo[T]
     |     object Foo { 
     |       implicit def FooString = new Foo[String] { 
     |         override def toString = "it's me!"
     |       } 
     |     }
     |   }
     | }
defined object Module

scala> implicitly[Module.Foo[String]]
res0: Module.Foo[String] = it's me!

scala> Module.Foo.pubMethod
res1: String = Hi!

scala> Module.Internal.Foo.FooString
<console>:13: error: object Internal in object Module cannot be accessed in object Module
       Module.Internal.Foo.FooString
              ^

Is it a feature? Is it a bug? No one knows :sweat_smile:

3 Likes

Maybe this is analogous to the previous example:

package escapes {
  object X {
    def x = new Y
  }
  private[escapes] class Y {
    def y = 42
  }
}
package escaped {
  object Main extends App {
    println(escapes.X.x.y)
  }
}

The private type seems to leak a member in the same way. The spec on implicit scope doesn’t talk about access, but making the implicit private makes it invisible.

Why isn’t def x = new Y an error “private type Y escape its scope” or
something like that. I’ve gotten errors like that in cases that would seem
to be comparable.

This is rather tool problem, not a language. Implicit function is a function and disallowing to call it will miselid people.

And last question… what problem we want to solve? If list of functions/vals like ev then sorry… they just should have meaningful names.

1 Like