Context boundaries for type members or how to defer implicit permission until a member instance is created

In the following example, is there a way to avoid implicit permission choosing defaultInstance and using intInstance ? More background after code:

 // the following part is an external fixed API trait TypeCls[A] { def foo: String } object TypeCls { def foo[A](implicit x: TypeCls[A]) = x.foo implicit def defaultInstance[A]: TypeCls[A] = new TypeCls[A] { def foo = "default" } implicit val intInstance: TypeCls[Int] = new TypeCls[Int] { def foo = "integer" } } trait FooM { type A def foo: String = implicitly[TypeCls[A]].foo } // end of external fixed API class FooP[A:TypeCls] { // with type params, we can use context bound def foo: String = implicitly[TypeCls[A]].foo } class MyFooP extends FooP[Int] class MyFooM extends FooM { type A = Int } object Main extends App { println(s"With type parameter: ${(new MyFooP).foo}") println(s"With type member: ${(new MyFooM).foo}") } 

Actual output:

 With type parameter: integer With type member: default 

Required Conclusion:

 With type parameter: integer With type member: integer 

I am working with a third-party library that uses the above scheme to provide default instances for a class of type TypeCls . I think the above code is a minimal example demonstrating my problem.

It is assumed that users should mix with the FooM and instantiate the abstract element A The problem is that because of defaultInstance calling (new MyFooM).foo does not allow the specialized intInstance and instead makes defaultInstance , which is not what I want.

I added an alternative version using type parameters called FooP (P = Parameter, M = Member), which avoids defaultInstance by using context binding to the type parameter.

Is there an equivalent way to do this with type members?

EDIT: I have an error in my simplification, in fact foo not def , but val , so it is not possible to add an implicit parameter. Thus, no current answers are applicable.

 trait FooM { type A val foo: String = implicitly[TypeCls[A]].foo } // end of external fixed API class FooP[A:TypeCls] { // with type params, we can use context bound val foo: String = implicitly[TypeCls[A]].foo } 
+5
source share
2 answers

The simplest solution in this particular case is that foo itself requires an implicit instance of TypeCls[A] . The only drawback is that it will be passed on every call to foo , and not just when creating an instance of FooM . Therefore, each time you call foo you must make sure that they are in scope. Although as long as TypeCls are in the companion object, you wonโ€™t have anything special.

 trait FooM { type A def foo(implicit e: TypeCls[A]): String = e.foo } 

UPDATE In my previous answer, I was able to miss the fact that FooM cannot be changed. Also, the last edit of the question means that FooM.foo is actually val , not def .

The good news is that the API you are using is simply broken. There is no way to ever return anything useful (it will always allow TypeCls[A] to TypeCls.defaultInstance regardless of the actual value of A ). The only way out is to override foo in the derived class, where the actual value of A known, in order to be able to use the corresponding TypeCls instance. Fortunately, this idea can be combined with your original workaround by using a context-bound class ( FooP in your case):

 class FooMEx[T:TypeCls] extends FooM { type A = T override val foo: String = implicitly[TypeCls[A]].foo } 

Now, instead of having your classes extend FooM directly, extend them with FooMEx :

 class MyFoo extends FooMEx[Int] 

The only difference between FooMEx and your original FooP class is that FooMEx makes the FooM extension, so MyFoo is a proper instance of FooM and can thus be used with a fixed API.

+1
source

You can copy the code from a third-party library. Overriding a method does the trick.

 class MyFooM extends FooM { type A = Int override def foo: String = implicitly[TypeCls[A]].foo} 

It's a hack, but I doubt there's anything better.

I do not know why this works the way it is. This must be some order in which the type alias is replaced in an implicit expression.

Only a specialist in the language specification can tell you the exact reason.

+1
source

Source: https://habr.com/ru/post/1236234/


All Articles