Providing implicit instances in the descendant of the type they need

To my surprise, I found out that the compiler cannot resolve the implicit in the following example:

trait API { def f(implicit a: Int) = ??? } class Impl extends API { implicit val int = 2 } (new Impl).f 

As in

 class Impl extends API object Impl { implicit val int = 2 } 

I am deeply disappointed with this. Why is this and is there any workaround for this? I should note that importing the implicit into the outer scope is not an option, since the sole purpose of the above is to Impl implementation details from the Impl user.

The above is a generalization of the template in which API developers provide specific instances of typeclass that are used by functions implemented in the API. The end user works with implementations.

+6
source share
3 answers

Good. This is solvable. The problem is obviously related to implicit resolution. We need to somehow add an implicit declaration to it. According to this answer, one area is the implicit area of ​​type arguments. So what we will do is muscle in it.

Obviously, we cannot do anything with implicit domains of type Int , but we can with the types that we define. So, to solve this problem, the trick is to use the phantom type to define an instance of the typeclass class that will provide us with what we need.

 // The typeclass, which we'll resolve implicitly. trait IntResolver[ instancesProvider ]{ val int: Int } // Notice the `instancesProvider` parameter. trait Trait[ instancesProvider ] { def f( implicit intResolver: IntResolver[ instancesProvider ] ) = intResolver.int * 2 } // We specify `Impl` as a type-parameter to `Trait` to explicitly state // that the compiler should include the implicit scope of `Impl` in its search // for instances. class Impl extends Trait[ Impl ] object Impl { implicit val intResolver = new IntResolver[ Impl ] { val int = 4 } } assert( (new Impl).f == 8 ) 

This approach is scalable to solve more complex cases with types, but you will need modified versions of these types, for example. instead of Show[a] you need Show[instancesProvider, a] .


What is this template useful for?

Here is an example in the spirit of how it will be used in the upcoming SORM 0.4 to test to support certain operations on certain types of certain drivers at the level :

 trait API[ driver ]{ def regex [ ref, value ] ( ref: ref, value: value ) ( implicit compiler: RegexCompiler[ driver, ref, value ] ) = sys.error("The function is implemeneted here") } // A typeclass with ops, that don't matter in this example trait RegexCompiler[ driver, ref, value ] class Mysql extends API[ Mysql ] object Mysql { implicit def stringRegexCompiler[ ref ] = new RegexCompiler[ Mysql, ref, String ] {} } class CouchDB extends API[ CouchDB ] object CouchDB { // This driver has no support for regex, so there no instance } (new Mysql).regex("someref", "a") // compiles fine (new Mysql).regex("someref", 2) // doesn't compile (new CouchDB).regex("someref", "a") // doesn't compile 
0
source

How about this?

 trait API { implicit val i: Int protected def fImplementation(implicit a: Int) = a * 2 def f = fImplementation } class Impl extends API { implicit val i = 2 } (new Impl).f // <-- should be Int(4) 
+3
source

The problem is that your Int int not in the implicit scope at the moment you call the function:

 (new Impl).f 

The compiler will not look inside the class for implicits used when calling other members of this class.

You can use the Chris solution, or you can:

 val newImpl = newImpl import newImpl.int newImpl.f 

Or ... If you want the value inside the class to be the default, but it has the ability to override it from the outside, then implicit parameters can also be default parameters:

 trait Api { def defaultInt: Int def f(implicit a: Int = defaultInt) = ??? } class Impl extends Api { val defaultInt = 2 } 

UPDATE

Based on the commentary on your own answer, implicit chaining is also implied, which is possible here:

 trait HasInt { def intVal: Int } sealed trait HasIntTypeClass[T] extends HasInt implicit object StringHasInt extends HasIntTypeClass[String] { val intVal = 42 } trait Api { def defaultHasInt: HasInt def f(implicit a: HasInt = defaultHasInt): Int = a.intVal * 2 } class Impl extends Api { def defaultHasInt: HasInt = implicitly[HasIntTypeClass[String]] } 

In any case, put the type parameter on defaultHasInt , if necessary.

+3
source

All Articles